diff --git a/akonadi/collectionfetchjob.cpp b/akonadi/collectionfetchjob.cpp index b49351453..2631fa2aa 100644 --- a/akonadi/collectionfetchjob.cpp +++ b/akonadi/collectionfetchjob.cpp @@ -1,227 +1,241 @@ /* Copyright (c) 2006 - 2007 Volker Krause 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 "collectionfetchjob.h" #include "imapparser_p.h" #include "job_p.h" #include "protocol_p.h" #include "protocolhelper_p.h" #include "entity_p.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate( CollectionFetchJob *parent ) : JobPrivate( parent ), mUnsubscribed( false ), mStatistics( false ) { } Q_DECLARE_PUBLIC( CollectionFetchJob ) CollectionFetchJob::Type mType; Collection mBase; Collection::List mBaseList; Collection::List mCollections; QString mResource; + QList mMimeTypes; Collection::List mPendingCollections; QTimer *mEmitTimer; bool mUnsubscribed; bool mStatistics; void timeout() { Q_Q( CollectionFetchJob ); mEmitTimer->stop(); // in case we are called by result() if ( !mPendingCollections.isEmpty() ) { emit q->collectionsReceived( mPendingCollections ); mPendingCollections.clear(); } } }; CollectionFetchJob::CollectionFetchJob( const Collection &collection, Type type, QObject *parent ) : Job( new CollectionFetchJobPrivate( this ), parent ) { Q_D( CollectionFetchJob ); d->mBase = collection; d->mType = type; d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); } CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, QObject * parent ) : Job( new CollectionFetchJobPrivate( this ), parent ) { Q_D( CollectionFetchJob ); Q_ASSERT( !cols.isEmpty() ); if ( cols.size() == 1 ) { d->mBase = cols.first(); d->mType = CollectionFetchJob::Base; } else { d->mBaseList = cols; } d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); } CollectionFetchJob::~CollectionFetchJob() { } Collection::List CollectionFetchJob::collections() const { Q_D( const CollectionFetchJob ); return d->mCollections; } void CollectionFetchJob::doStart() { Q_D( CollectionFetchJob ); if ( !d->mBaseList.isEmpty() ) { foreach ( const Collection &col, d->mBaseList ) { new CollectionFetchJob( col, CollectionFetchJob::Base, this ); } return; } if ( !d->mBase.isValid() && d->mBase.remoteId().isEmpty() ) { setError( Unknown ); setErrorText( QLatin1String( "Invalid collection given." ) ); emitResult(); return; } QByteArray command = d->newTag(); if ( !d->mBase.isValid() ) command += " " AKONADI_CMD_RID; if ( d->mUnsubscribed ) command += " X-AKLIST "; else command += " X-AKLSUB "; if ( d->mBase.isValid() ) command += QByteArray::number( d->mBase.id() ); else command += ImapParser::quote( d->mBase.remoteId().toUtf8() ); command += ' '; switch ( d->mType ) { case Base: command += "0 ("; break; case FirstLevel: command += "1 ("; break; case Recursive: command += "INF ("; break; default: Q_ASSERT( false ); } if ( !d->mResource.isEmpty() ) { command += "RESOURCE \""; command += d->mResource.toUtf8(); command += '"'; } + if ( !d->mMimeTypes.isEmpty() ) { + command += " MIMETYPE ("; + command += ImapParser::join( d->mMimeTypes, " " ); + command += ')'; + } + if ( d->mStatistics ) { command += ") (STATISTICS true"; } command += ")\n"; d->writeData( command ); } void CollectionFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) { Q_D( CollectionFetchJob ); if ( tag == "*" ) { Collection collection; ProtocolHelper::parseCollection( data, collection ); if ( !collection.isValid() ) return; collection.d_ptr->resetChangeLog(); d->mCollections.append( collection ); d->mPendingCollections.append( collection ); if ( !d->mEmitTimer->isActive() ) d->mEmitTimer->start(); return; } kDebug( 5250 ) << "Unhandled server response" << tag << data; } void CollectionFetchJob::setResource(const QString & resource) { Q_D( CollectionFetchJob ); d->mResource = resource; } +void CollectionFetchJob::setContentMimeTypes( const QStringList &contentMimeTypes ) +{ + Q_D( CollectionFetchJob ); + foreach ( const QString &mt, contentMimeTypes ) + d->mMimeTypes.append( mt.toUtf8() ); +} + void CollectionFetchJob::slotResult(KJob * job) { Q_D( CollectionFetchJob ); CollectionFetchJob *list = dynamic_cast( job ); Q_ASSERT( job ); d->mCollections += list->collections(); Job::slotResult( job ); if ( !job->error() && !hasSubjobs() ) emitResult(); } void CollectionFetchJob::includeUnsubscribed(bool include) { Q_D( CollectionFetchJob ); d->mUnsubscribed = include; } void CollectionFetchJob::includeStatistics(bool include) { Q_D( CollectionFetchJob ); d->mStatistics = include; } #include "collectionfetchjob.moc" diff --git a/akonadi/collectionfetchjob.h b/akonadi/collectionfetchjob.h index d18e88050..10c140766 100644 --- a/akonadi/collectionfetchjob.h +++ b/akonadi/collectionfetchjob.h @@ -1,148 +1,157 @@ /* Copyright (c) 2006 - 2007 Volker Krause 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_COLLECTIONFETCHJOB_H #define AKONADI_COLLECTIONFETCHJOB_H #include "akonadi_export.h" #include #include namespace Akonadi { class CollectionFetchJobPrivate; /** * @short Job that fetches collections from the Akonadi storage. * * This class can be used to retrieve the complete or partial collection tree * from the Akonadi storage. * * @code * * using namespace Akonadi; * * // fetching all collections recursive, starting at the root collection * CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); * if ( job->exec() ) { * Collection::List collections = job->collections(); * foreach( const Collection &collection, collections ) { * qDebug() << "Name:" << collection.name(); * } * } * * @endcode * * @author Volker Krause */ class AKONADI_EXPORT CollectionFetchJob : public Job { Q_OBJECT public: /** * Describes the type of fetch depth. */ enum Type { Base, ///< Only fetch the base collection. FirstLevel, ///< Only list direct sub-collections of the base collection. Recursive ///< List all sub-collections. }; /** * Creates a new collection fetch job. If the given base collection * has a unique identifier, this is used to identify the collection in the * Akonadi server. If only a remote identifier is avaiable the collection * is identified using that, given a resource search context has been * specified. There two ways of doing that: by calling setResource() * or globally using Akonadi::ResourceSelectJob. * * @param collection The base collection for the listing. * @param type The type of fetch depth. * @param parent The parent object. */ explicit CollectionFetchJob( const Collection &collection, Type type = FirstLevel, QObject *parent = 0 ); /** * Creates a new collection fetch job to retrieve a list of collections. * The same rules for identifiers apply as noted in the constructor above. * * @param collections A list of collections to fetch. Must not be empty. * @param parent The parent object. */ explicit CollectionFetchJob( const Collection::List &collections, QObject *parent = 0 ); /** * Destroys the collection fetch job. */ virtual ~CollectionFetchJob(); /** * Returns the list of fetched collection. */ Collection::List collections() const; /** * Sets a resource identifier to limit collection listing to one resource. * * @param resource The resource identifier. */ void setResource( const QString &resource ); + /** + * Sets a content mimetype filter, that is include only collections in the result + * that match the given filter. + * @param contentMimeTypes A list of content mimetypes. + * + * @since 4.4 + */ + void setContentMimeTypes( const QStringList &contentMimeTypes ); + /** * Include also unsubscribed collections. */ void includeUnsubscribed( bool include = true ); /** * Include also statistics about the collections. * * @since 4.3 */ void includeStatistics( bool include = true ); Q_SIGNALS: /** * This signal is emitted whenever the job has received collections. * * @param collections The received collections. */ void collectionsReceived( const Akonadi::Collection::List &collections ); protected: virtual void doStart(); virtual void doHandleResponse( const QByteArray &tag, const QByteArray &data ); protected Q_SLOTS: //@cond PRIVATE void slotResult( KJob* job ); //@endcond private: Q_DECLARE_PRIVATE( CollectionFetchJob ) //@cond PRIVATE Q_PRIVATE_SLOT( d_func(), void timeout() ) //@endcond }; } #endif diff --git a/akonadi/control.cpp b/akonadi/control.cpp index 42d20582d..92af7ea83 100644 --- a/akonadi/control.cpp +++ b/akonadi/control.cpp @@ -1,258 +1,258 @@ /* Copyright (c) 2007 Volker Krause 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 "control.h" #include "servermanager.h" #include "ui_controlprogressindicator.h" #include "selftestdialog_p.h" #include "erroroverlay_p.h" #include "firstrun_p.h" #include #include #include #include #include #include #include using namespace Akonadi; class ControlProgressIndicator : public QFrame { public: ControlProgressIndicator( QWidget *parent = 0 ) : QFrame( parent ) { setWindowModality( Qt::ApplicationModal ); resize( 400, 100 ); setWindowFlags( Qt::FramelessWindowHint | Qt::Dialog ); ui.setupUi( this ); setFrameShadow( QFrame::Plain ); setFrameShape( QFrame::Box ); } void setMessage( const QString &msg ) { ui.statusLabel->setText( msg ); } Ui::ControlProgressIndicator ui; }; +class StaticControl : public Control +{ + public: + StaticControl() : Control() {} +}; + +K_GLOBAL_STATIC( StaticControl, s_instance ) + /** * @internal */ class Control::Private { public: Private( Control *parent ) : mParent( parent ), mEventLoop( 0 ), mProgressIndicator( 0 ), mFirstRunner( 0 ), mSuccess( false ), mStarting( false ), mStopping( false ) { KGlobal::locale()->insertCatalog( QString::fromLatin1("libakonadi") ); if ( ServerManager::isRunning() ) mFirstRunner = new Firstrun( mParent ); } ~Private() { delete mProgressIndicator; } void setupProgressIndicator( const QString &msg, QWidget *parent = 0 ) { if ( mProgressIndicator ) return; mProgressIndicator = new ControlProgressIndicator( parent ); mProgressIndicator->setMessage( msg ); } void createErrorOverlays() { foreach ( QWidget* widget, mPendingOverlays ) new ErrorOverlay( widget ); mPendingOverlays.clear(); } + void cleanup() + { + s_instance.destroy(); + } + bool exec(); void serverStarted(); void serverStopped(); QPointer mParent; QEventLoop *mEventLoop; QPointer mProgressIndicator; QList mPendingOverlays; Firstrun *mFirstRunner; bool mSuccess; bool mStarting; bool mStopping; }; -class StaticControl : public Control -{ - public: - StaticControl() : Control() {} -}; - -K_GLOBAL_STATIC( StaticControl, s_instance ) - -void Control::cleanup() -{ - s_instance.destroy(); -} - bool Control::Private::exec() { if ( mProgressIndicator ) mProgressIndicator->show(); kDebug( 5250 ) << "Starting Akonadi (using an event loop)."; mEventLoop = new QEventLoop( mParent ); // safety timeout QTimer::singleShot( 10000, mEventLoop, SLOT(quit()) ); mEventLoop->exec(); mEventLoop->deleteLater(); mEventLoop = 0; if ( !mSuccess ) { kWarning( 5250 ) << "Could not start/stop Akonadi!"; if ( mProgressIndicator && mStarting ) { QPointer dlg = new SelfTestDialog( mProgressIndicator->parentWidget() ); dlg->exec(); delete dlg; - if ( !mParent ) + if ( !mParent ) return false; } } delete mProgressIndicator; mProgressIndicator = 0; mStarting = false; mStopping = false; const bool rv = mSuccess; mSuccess = false; return rv; } void Control::Private::serverStarted() { if ( mEventLoop && mEventLoop->isRunning() && mStarting ) { mEventLoop->quit(); mSuccess = true; } if ( !mFirstRunner ) mFirstRunner = new Firstrun( mParent ); } void Control::Private::serverStopped() { if ( mEventLoop && mEventLoop->isRunning() && mStopping ) { mEventLoop->quit(); mSuccess = true; } } Control::Control() : d( new Private( this ) ) { - connect( ServerManager::self(), SIGNAL(started()), SLOT(serverStarted()) ); - connect( ServerManager::self(), SIGNAL(stopped()), SLOT(serverStopped()) ); + connect( ServerManager::self(), SIGNAL( started() ), SLOT( serverStarted() ) ); + connect( ServerManager::self(), SIGNAL( stopped() ), SLOT( serverStopped() ) ); // mProgressIndicator is a widget, so it better be deleted before the QApplication is deleted // Otherwise we get a crash in QCursor code with Qt-4.5 if ( QCoreApplication::instance() ) - connect( QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanup()) ); + connect( QCoreApplication::instance(), SIGNAL( aboutToQuit() ), this, SLOT( cleanup() ) ); } Control::~Control() { delete d; } bool Control::start() { if ( s_instance->d->mStopping ) return false; if ( ServerManager::isRunning() || s_instance->d->mEventLoop ) return true; s_instance->d->mStarting = true; if ( !ServerManager::start() ) return false; return s_instance->d->exec(); } bool Control::stop() { if ( s_instance->d->mStarting ) return false; if ( !ServerManager::isRunning() || s_instance->d->mEventLoop ) return true; s_instance->d->mStopping = true; if ( !ServerManager::stop() ) return false; return s_instance->d->exec(); } bool Control::restart() { if ( ServerManager::isRunning() ) { if ( !stop() ) return false; } return start(); } bool Control::start(QWidget * parent) { s_instance->d->setupProgressIndicator( i18n( "Starting Akonadi server..." ), parent ); return start(); } bool Control::stop(QWidget * parent) { s_instance->d->setupProgressIndicator( i18n( "Stopping Akonadi server..." ), parent ); return stop(); } bool Control::restart(QWidget * parent) { if ( ServerManager::isRunning() ) { if ( !stop( parent ) ) return false; } return start( parent ); } void Control::widgetNeedsAkonadi(QWidget * widget) { s_instance->d->mPendingOverlays.append( widget ); // delay the overlay creation since we rely on widget being reparented // correctly already QTimer::singleShot( 0, s_instance, SLOT(createErrorOverlays()) ); } #include "control.moc" diff --git a/akonadi/control.h b/akonadi/control.h index 4e1ce9dc2..22a8a73db 100644 --- a/akonadi/control.h +++ b/akonadi/control.h @@ -1,145 +1,143 @@ /* Copyright (c) 2007 Volker Krause 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_CONTROL_H #define AKONADI_CONTROL_H #include "akonadi_export.h" #include namespace Akonadi { /** * @short Provides methods to control the Akonadi server process. * * This class provides high-level methods to control the Akonadi * server. These methods are synchronously (ie. use a sub-eventloop) * and can show dialogs. For more low-level methods see * Akonadi::ServerManager. * * While the Akonadi server normally is started by the KDE session * manager, it is not guaranteed that your application is running * inside a KDE session. Therefore it is recommended to execute * Akonadi::Control::start() during startup to ensure the Akonadi * server is running. * * Example: * * @code * * if ( !Akonadi::Control::start() ) { * qDebug() << "Unable to start Akonadi server, exit application"; * return 1; * } else { * ... * } * * @endcode * * @author Volker Krause * * @see Akonadi::ServerManager */ class AKONADI_EXPORT Control : public QObject { Q_OBJECT public: /** * Destroys the control object. */ ~Control(); /** * Starts the Akonadi server synchronously if it is not already running. * @return @c true if the server was started successfully or was already * running, @c false otherwise */ static bool start(); /** * Same as start(), but with GUI feedback. * @param parent The parent widget. * @since 4.2 */ static bool start( QWidget *parent ); /** * Stops the Akonadi server synchronously if it is currently running. * @return @c true if the server was shutdown successfully or was * not running at all, @c false otherwise. * @since 4.2 */ static bool stop(); /** * Same as stop(), but with GUI feedback. * @param parent The parent widget. * @since 4.2 */ static bool stop( QWidget *parent ); /** * Restarts the Akonadi server synchronously. * @return @c true if the restart was successful, @c false otherwise, * the server state is undefined in this case. * @since 4.2 */ static bool restart(); /** * Same as restart(), but with GUI feedback. * @param parent The parent widget. * @since 4.2 */ static bool restart( QWidget *parent ); /** * Disable the given widget when Akonadi is not operational and show * an error overlay (given enough space). Cascading use is automatically * detected. * @param widget The widget depending on Akonadi being operational. * @since 4.2 */ static void widgetNeedsAkonadi( QWidget *widget ); protected: /** * Creates the control object. */ Control(); - private Q_SLOTS: - void cleanup(); - private: //@cond PRIVATE class Private; Private* const d; Q_PRIVATE_SLOT( d, void serverStarted() ) Q_PRIVATE_SLOT( d, void serverStopped() ) Q_PRIVATE_SLOT( d, void createErrorOverlays() ) + Q_PRIVATE_SLOT( d, void cleanup() ) //@endcond }; } #endif diff --git a/akonadi/session_p.h b/akonadi/session_p.h index 18c0d208a..7565767e5 100644 --- a/akonadi/session_p.h +++ b/akonadi/session_p.h @@ -1,115 +1,115 @@ /* Copyright (c) 2007 Volker Krause 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_SESSION_P_H #define AKONADI_SESSION_P_H #include "session.h" #include "imapparser_p.h" #include #include #include #include class QLocalSocket; namespace Akonadi { /** * @internal */ class SessionPrivate { public: SessionPrivate( Session *parent ) : mParent( parent ), mConnectionSettings( 0 ), protocolVersion( 0 ) { parser = new ImapParser(); } ~SessionPrivate() { delete parser; delete mConnectionSettings; } void startNext(); void reconnect(); void socketDisconnected(); void socketError( QLocalSocket::LocalSocketError error ); void dataReceived(); void doStartNext(); void startJob( Job* job ); void jobDone( KJob* job ); void jobWriteFinished( Akonadi::Job* job ); void jobDestroyed( QObject *job ); bool canPipelineNext(); /** * Creates a new default session for this thread with * the given @p sessionId. The session can be accessed * later by defaultSession(). * * You only need to call this method if you want that the * default session has a special custom id, otherwise a random unique * id is used automatically. */ static void createDefaultSession( const QByteArray &sessionId ); /** Associates the given Job object with this session. */ void addJob( Job* job ); /** Returns the next IMAP tag. */ int nextTag(); /** Sends the given raw data. */ void writeData( const QByteArray &data ); - static int minimumProtocolVersion() { return 15; } + static int minimumProtocolVersion() { return 16; } Session *mParent; QByteArray sessionId; QSettings *mConnectionSettings; QLocalSocket* socket; bool connected; int theNextTag; int protocolVersion; // job management QQueue queue; QQueue pipeline; Job* currentJob; bool jobRunning; // parser stuff ImapParser *parser; }; } #endif diff --git a/akonadi/tests/collectionjobtest.cpp b/akonadi/tests/collectionjobtest.cpp index add714360..d50f7ac6c 100644 --- a/akonadi/tests/collectionjobtest.cpp +++ b/akonadi/tests/collectionjobtest.cpp @@ -1,604 +1,625 @@ /* Copyright (c) 2006 Volker Krause 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 #include "collectionjobtest.h" #include #include "test_utils.h" #include "testattribute.h" #include "agentmanager.h" #include "agentinstance.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collection.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "collectionselectjob_p.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "collectionpathresolver_p.h" #include "collectionutils_p.h" #include "control.h" #include "item.h" #include "kmime/messageparts.h" #include "resourceselectjob_p.h" #include #include using namespace Akonadi; QTEST_AKONADIMAIN( CollectionJobTest, NoGUI ) void CollectionJobTest::initTestCase() { qRegisterMetaType(); AttributeFactory::registerAttribute(); Control::start(); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } static Collection findCol( const Collection::List &list, const QString &name ) { foreach ( const Collection &col, list ) if ( col.name() == name ) return col; return Collection(); } // list compare which ignores the order template static void compareLists( const QList &l1, const QList &l2 ) { QCOMPARE( l1.count(), l2.count() ); foreach ( const T entry, l1 ) { QVERIFY( l2.contains( entry ) ); } } template static T* extractAttribute( QList attrs ) { T dummy; foreach ( Attribute* attr, attrs ) { if ( attr->type() == dummy.type() ) return dynamic_cast( attr ); } return 0; } static Collection::Id res1ColId = 6; // -1; static Collection::Id res2ColId = 7; //-1; static Collection::Id res3ColId = -1; static Collection::Id searchColId = -1; void CollectionJobTest::testTopLevelList( ) { // non-recursive top-level list CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); // check if everything is there and has the correct types and attributes QCOMPARE( list.count(), 4 ); Collection col; col = findCol( list, "res1" ); QVERIFY( col.isValid() ); res1ColId = col.id(); // for the next test QVERIFY( res1ColId > 0 ); QVERIFY( CollectionUtils::isResource( col ) ); QCOMPARE( col.parent(), Collection::root().id() ); QCOMPARE( col.resource(), QLatin1String("akonadi_knut_resource_0") ); QVERIFY( findCol( list, "res2" ).isValid() ); res2ColId = findCol( list, "res2" ).id(); QVERIFY( res2ColId > 0 ); QVERIFY( findCol( list, "res3" ).isValid() ); res3ColId = findCol( list, "res3" ).id(); QVERIFY( res3ColId > 0 ); col = findCol( list, "Search" ); searchColId = col.id(); QVERIFY( col.isValid() ); QVERIFY( CollectionUtils::isVirtualParent( col ) ); QCOMPARE( col.resource(), QLatin1String("akonadi_search_resource") ); } void CollectionJobTest::testFolderList( ) { // recursive list of physical folders CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Recursive ); QSignalSpy spy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); QVERIFY( spy.isValid() ); QVERIFY( job->exec() ); Collection::List list = job->collections(); int count = 0; for ( int i = 0; i < spy.count(); ++i ) { Collection::List l = spy[i][0].value(); for ( int j = 0; j < l.count(); ++j ) { QVERIFY( list.count() > count + j ); QCOMPARE( list[count + j].id(), l[j].id() ); } count += l.count(); } QCOMPARE( count, list.count() ); // check if everything is there QCOMPARE( list.count(), 4 ); Collection col; QStringList contentTypes; col = findCol( list, "foo" ); QVERIFY( col.isValid() ); QCOMPARE( col.parent(), res1ColId ); QVERIFY( CollectionUtils::isFolder( col ) ); contentTypes << "message/rfc822" << "text/calendar" << "text/directory" << "application/octet-stream" << "inode/directory"; compareLists( col.contentMimeTypes(), contentTypes ); QVERIFY( findCol( list, "bar" ).isValid() ); QCOMPARE( findCol( list, "bar" ).parent(), col.id() ); QVERIFY( findCol( list, "bla" ).isValid() ); } void CollectionJobTest::testNonRecursiveFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Base ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 1 ); QVERIFY( findCol( list, "res1" ).isValid() ); } void CollectionJobTest::testEmptyFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testSearchFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( searchColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testResourceFolderList() { // non-existing resource CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->setResource( "i_dont_exist" ); QVERIFY( !job->exec() ); // recursive listing of all collections of an existing resource job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 5 ); QVERIFY( findCol( list, "res1" ).isValid() ); QVERIFY( findCol( list, "foo" ).isValid() ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); int fooId = findCol( list, "foo" ).id(); // limited listing of a resource job = new CollectionFetchJob( Collection( fooId ), CollectionFetchJob::Recursive ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); list = job->collections(); QCOMPARE( list.count(), 3 ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); } +void CollectionJobTest::testMimeTypeFilter() +{ + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); + job->setContentMimeTypes( QStringList() << "message/rfc822" ); + AKVERIFYEXEC( job ); + + Collection::List list = job->collections(); + QCOMPARE( list.count(), 2 ); + QVERIFY( findCol( list, "res1" ).isValid() ); + QVERIFY( findCol( list, "foo" ).isValid() ); + int fooId = findCol( list, "foo" ).id(); + + // limited listing of a resource + job = new CollectionFetchJob( Collection( fooId ), CollectionFetchJob::Recursive ); + job->setContentMimeTypes( QStringList() << "message/rfc822" ); + AKVERIFYEXEC( job ); + + list = job->collections(); + QCOMPARE( list.count(), 0 ); +} + void CollectionJobTest::testCreateDeleteFolder_data() { QTest::addColumn("collection"); QTest::addColumn("creatable"); Collection col; QTest::newRow("empty") << col << false; col.setName( "new folder" ); col.setParent( res3ColId ); QTest::newRow("simple") << col << true; col.setParent( res3ColId ); col.setName( "foo" ); QTest::newRow( "existing in different resource" ) << col << true; col.setName( "mail folder" ); QStringList mimeTypes; mimeTypes << "inode/directory" << "message/rfc822"; col.setContentMimeTypes( mimeTypes ); col.setRemoteId( "remote id" ); CachePolicy policy; policy.setInheritFromParent( false ); policy.setIntervalCheckTime( 60 ); policy.setLocalParts( QStringList( MessagePart::Envelope ) ); policy.setSyncOnDemand( true ); policy.setCacheTimeout( 120 ); col.setCachePolicy( policy ); QTest::newRow( "complex" ) << col << true; col = Collection(); col.setName( "New Folder" ); col.setParent( searchColId ); QTest::newRow( "search folder" ) << col << false; col.setParent( res2ColId ); col.setName( "foo2" ); QTest::newRow( "already existing" ) << col << false; col.setName( "Bla" ); col.setParent( 2 ); QTest::newRow( "already existing with different case" ) << col << true; CollectionPathResolver *resolver = new CollectionPathResolver( "res2/foo2", this ); QVERIFY( resolver->exec() ); col.setParent( resolver->collection() ); col.setName( "new folder" ); QTest::newRow( "parent noinferior" ) << col << false; col.setParent( INT_MAX ); QTest::newRow( "missing parent" ) << col << false; col = Collection(); col.setName( "rid parent" ); col.setParentRemoteId( "8" ); QTest::newRow( "rid parent" ) << col << false; // missing resource context } void CollectionJobTest::testCreateDeleteFolder() { QFETCH( Collection, collection ); QFETCH( bool, creatable ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); QCOMPARE( createJob->exec(), creatable ); if ( !creatable ) return; Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); QCOMPARE( createdCol.parent(), collection.parent() ); QCOMPARE( createdCol.remoteId(), collection.remoteId() ); QCOMPARE( createdCol.cachePolicy(), collection.cachePolicy() ); CollectionFetchJob *listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.remoteId(), collection.remoteId() ); QCOMPARE( listedCol.cachePolicy(), collection.cachePolicy() ); // fetch parent to compare inherited collection properties Collection parentCol = Collection::root(); if ( collection.parent() > 0 ) { CollectionFetchJob *listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::Base, this ); AKVERIFYEXEC( listJob ); QCOMPARE( listJob->collections().count(), 1 ); parentCol = listJob->collections().first(); } if ( collection.contentMimeTypes().isEmpty() ) compareLists( listedCol.contentMimeTypes(), parentCol.contentMimeTypes() ); else compareLists( listedCol.contentMimeTypes(), collection.contentMimeTypes() ); if ( collection.resource().isEmpty() ) QCOMPARE( listedCol.resource(), parentCol.resource() ); else QCOMPARE( listedCol.resource(), collection.resource() ); CollectionDeleteJob *delJob = new CollectionDeleteJob( createdCol, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( Collection( collection.parent() ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } void CollectionJobTest::testIllegalDeleteFolder() { // non-existing folder CollectionDeleteJob *del = new CollectionDeleteJob( Collection( INT_MAX ), this ); QVERIFY( !del->exec() ); // root del = new CollectionDeleteJob( Collection::root(), this ); QVERIFY( !del->exec() ); } void CollectionJobTest::testStatistics() { // empty folder CollectionStatisticsJob *statistics = new CollectionStatisticsJob( Collection( res1ColId ), this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // folder with attributes and content CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); statistics = new CollectionStatisticsJob( Collection( resolver->collection() ), this ); QVERIFY( statistics->exec() ); s = statistics->statistics(); QCOMPARE( s.count(), 15ll ); QCOMPARE( s.unreadCount(), 14ll ); } void CollectionJobTest::testModify_data() { QTest::addColumn( "uid" ); QTest::addColumn( "rid" ); QTest::newRow( "uid" ) << collectionIdFromPath( "res1/foo" ) << QString(); QTest::newRow( "rid" ) << -1ll << QString( "10" ); } #define RESET_COLLECTION_ID \ col.setId( uid ); \ if ( !rid.isEmpty() ) col.setRemoteId( rid ) void CollectionJobTest::testModify() { QFETCH( qint64, uid ); QFETCH( QString, rid ); if ( !rid.isEmpty() ) { ResourceSelectJob *rjob = new ResourceSelectJob( "akonadi_knut_resource_0" ); AKVERIFYEXEC( rjob ); } QStringList reference; reference << "text/calendar" << "text/directory" << "message/rfc822" << "application/octet-stream" << "inode/directory"; Collection col; RESET_COLLECTION_ID; // test noop modify CollectionModifyJob *mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); CollectionFetchJob* ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // test clearing content types RESET_COLLECTION_ID; col.setContentMimeTypes( QStringList() ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QVERIFY( col.contentMimeTypes().isEmpty() ); // test setting contnet types RESET_COLLECTION_ID; col.setContentMimeTypes( reference ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // add attribute RESET_COLLECTION_ID; col.attribute( Collection::AddIfMissing )->data = "new"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "new" ) ); // modify existing attribute RESET_COLLECTION_ID; col.attribute()->data = "modified"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "modified" ) ); // renaming RESET_COLLECTION_ID; col.setName( "foo (renamed)" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QCOMPARE( col.name(), QString( "foo (renamed)" ) ); RESET_COLLECTION_ID; col.setName( "foo" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); } #undef RESET_COLLECTION_ID void CollectionJobTest::testIllegalModify() { // non-existing collection Collection col( INT_MAX ); col.setParent( res1ColId ); CollectionModifyJob *mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); // rename to already existing name col = Collection( res1ColId ); col.setName( "res2" ); mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); } void CollectionJobTest::testUtf8CollectionName() { QString folderName = QString::fromUtf8( "รค" ); // create collection Collection col; col.setParent( res3ColId ); col.setName( folderName ); CollectionCreateJob *create = new CollectionCreateJob( col, this ); QVERIFY( create->exec() ); col = create->collection(); QVERIFY( col.isValid() ); // list parent CollectionFetchJob *list = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::Recursive, this ); QVERIFY( list->exec() ); QCOMPARE( list->collections().count(), 1 ); QCOMPARE( col, list->collections().first() ); QCOMPARE( col.name(), folderName ); // modify collection col.setContentMimeTypes( QStringList( "message/rfc822'" ) ); CollectionModifyJob *modify = new CollectionModifyJob( col, this ); QVERIFY( modify->exec() ); // collection statistics CollectionStatisticsJob *statistics = new CollectionStatisticsJob( col, this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // delete collection CollectionDeleteJob *del = new CollectionDeleteJob( col, this ); QVERIFY( del->exec() ); } void CollectionJobTest::testMultiList() { Collection::List req; req << Collection( res1ColId ) << Collection( res2ColId ); CollectionFetchJob* job = new CollectionFetchJob( req, this ); QVERIFY( job->exec() ); Collection::List res; res = job->collections(); compareLists( res, req ); } void CollectionJobTest::testSelect() { CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); Collection col( resolver->collection() ); CollectionSelectJob *job = new CollectionSelectJob( col, this ); QVERIFY( job->exec() ); QCOMPARE( job->unseen(), -1 ); job = new CollectionSelectJob( col, this ); job->setRetrieveStatus( true ); QVERIFY( job->exec() ); QVERIFY( job->unseen() > -1 ); job = new CollectionSelectJob( Collection::root(), this ); QVERIFY( job->exec() ); job = new CollectionSelectJob( Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); } void CollectionJobTest::testRidFetch() { Collection col; col.setRemoteId( "10" ); CollectionFetchJob *job = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); job->setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); QCOMPARE( job->collections().count(), 1 ); col = job->collections().first(); QVERIFY( col.isValid() ); QCOMPARE( col.remoteId(), QString::fromLatin1( "10" ) ); } void CollectionJobTest::testRidCreateDelete() { Collection collection; collection.setName( "rid create" ); collection.setParentRemoteId( "8" ); collection.setRemoteId( "MY REMOTE ID" ); ResourceSelectJob *resSel = new ResourceSelectJob( "akonadi_knut_resource_2" ); AKVERIFYEXEC( resSel ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); AKVERIFYEXEC( createJob ); Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); CollectionFetchJob *listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.name(), collection.name() ); QVERIFY( !collection.isValid() ); CollectionDeleteJob *delJob = new CollectionDeleteJob( collection, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } #include "collectionjobtest.moc" diff --git a/akonadi/tests/collectionjobtest.h b/akonadi/tests/collectionjobtest.h index 3ecc3d6db..e704621d2 100644 --- a/akonadi/tests/collectionjobtest.h +++ b/akonadi/tests/collectionjobtest.h @@ -1,51 +1,52 @@ /* Copyright (c) 2006 Volker Krause 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 COLLECTIONJOBTEST_H #define COLLECTIONJOBTEST_H #include class CollectionJobTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testTopLevelList(); void testFolderList(); void testNonRecursiveFolderList(); void testEmptyFolderList(); void testSearchFolderList(); void testResourceFolderList(); + void testMimeTypeFilter(); void testCreateDeleteFolder_data(); void testCreateDeleteFolder(); void testIllegalDeleteFolder(); void testStatistics(); void testModify_data(); void testModify(); void testIllegalModify(); void testUtf8CollectionName(); void testMultiList(); void testSelect(); void testRidFetch(); void testRidCreateDelete(); }; #endif diff --git a/akonadi/tests/unittestenv/kdehome/share/config/kdedrc b/akonadi/tests/unittestenv/kdehome/share/config/kdedrc new file mode 100644 index 000000000..41d178141 --- /dev/null +++ b/akonadi/tests/unittestenv/kdehome/share/config/kdedrc @@ -0,0 +1,3 @@ +[General] +CheckSycoca=false +CheckFileStamps=false diff --git a/kimap/storejob.cpp b/kimap/storejob.cpp index ca068ec73..84da4efb2 100644 --- a/kimap/storejob.cpp +++ b/kimap/storejob.cpp @@ -1,179 +1,194 @@ /* Copyright (c) 2009 Kevin Ottens 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 "storejob.h" +#include #include #include "job_p.h" #include "message_p.h" #include "session_p.h" namespace KIMAP { class StoreJobPrivate : public JobPrivate { public: StoreJobPrivate( Session *session, const QString& name ) : JobPrivate( session, name ) { } ~StoreJobPrivate() { } ImapSet set; bool uidBased; StoreJob::StoreMode mode; MessageFlags flags; QMap resultingFlags; }; } using namespace KIMAP; StoreJob::StoreJob( Session *session ) : Job( *new StoreJobPrivate(session, i18n("Store")) ) { Q_D(StoreJob); d->mode = SetFlags; } StoreJob::~StoreJob() { } void StoreJob::setSequenceSet( const ImapSet &set ) { Q_D(StoreJob); d->set = set; } ImapSet StoreJob::sequenceSet() const { Q_D(const StoreJob); return d->set; } void StoreJob::setUidBased(bool uidBased) { Q_D(StoreJob); d->uidBased = uidBased; } bool StoreJob::isUidBased() const { Q_D(const StoreJob); return d->uidBased; } void StoreJob::setFlags( const MessageFlags &flags ) { Q_D(StoreJob); d->flags = flags; } MessageFlags StoreJob::flags() const { Q_D(const StoreJob); return d->flags; } void StoreJob::setMode( StoreMode mode ) { Q_D(StoreJob); d->mode = mode; } StoreJob::StoreMode StoreJob::mode() const { Q_D(const StoreJob); return d->mode; } QMap StoreJob::resultingFlags() const { Q_D(const StoreJob); return d->resultingFlags; } void StoreJob::doStart() { Q_D(StoreJob); QByteArray parameters = d->set.toImapSequenceSet()+' '; switch ( d->mode ) { case SetFlags: parameters+= "FLAGS"; break; case AppendFlags: parameters+= "+FLAGS"; break; case RemoveFlags: parameters+= "-FLAGS"; break; } parameters+=" ("; foreach ( const QByteArray &flag, d->flags ) { parameters+=flag+' '; } if (!d->flags.isEmpty()) parameters.chop(1); parameters+=')'; qDebug("%s", parameters.constData()); QByteArray command = "STORE"; if ( d->uidBased ) { command = "UID "+command; } d->tag = d->sessionInternal()->sendCommand( command, parameters ); } void StoreJob::handleResponse( const Message &response ) { Q_D(StoreJob); if (handleErrorReplies(response) == NotHandled ) { if ( response.content.size() == 4 && response.content[2].toString()=="FETCH" && response.content[3].type()==Message::Part::List ) { int id = response.content[1].toString().toInt(); + qint64 uid = 0; + bool uidFound = false; + QList resultingFlags; + QList content = response.content[3].toList(); for ( QList::ConstIterator it = content.constBegin(); it!=content.constEnd(); ++it ) { QByteArray str = *it; ++it; if ( str=="FLAGS" ) { if ( (*it).startsWith('(') && (*it).endsWith(')') ) { QByteArray str = *it; str.chop(1); str.remove(0, 1); - d->resultingFlags[id] = str.split(' '); + resultingFlags = str.split(' '); } else { - d->resultingFlags[id] << *it; + resultingFlags << *it; } + } else if ( str=="UID" ) { + uid = it->toLongLong(&uidFound); } } + + if ( !d->uidBased ) { + d->resultingFlags[id] = resultingFlags; + } else if ( uidFound ) { + d->resultingFlags[uid] = resultingFlags; + } else { + kWarning() << "We asked for UID but the server didn't give it back, resultingFlags not stored."; + } } } } #include "storejob.moc" diff --git a/kimap/tests/CMakeLists.txt b/kimap/tests/CMakeLists.txt index bd1aee84e..9f69663c8 100644 --- a/kimap/tests/CMakeLists.txt +++ b/kimap/tests/CMakeLists.txt @@ -1,54 +1,55 @@ include_directories( ${CMAKE_SOURCE_DIR}/kimap ${Boost_INCLUDE_DIR}) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) MACRO(KIMAP_UNIT_TESTS) FOREACH(_testname ${ARGN}) kde4_add_unit_test(${_testname} TESTNAME kimap-${_testname} NOGUI ${_testname}.cpp) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} kimap kmime) ENDFOREACH(_testname) ENDMACRO(KIMAP_UNIT_TESTS) MACRO(KIMAP_EXECUTABLE_TESTS) FOREACH(_testname ${ARGN}) kde4_add_executable(${_testname} NOGUI TEST ${_testname}.cpp) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} kimap kmime) ENDFOREACH(_testname) ENDMACRO(KIMAP_EXECUTABLE_TESTS) ### convenience macro MACRO(ADD_IMAPLIB_TEST _source) set(_test ${_source} fakeserver.cpp ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") get_filename_component(_name ${_source} NAME_WE) kde4_add_unit_test(${_name} TESTNAME kimap-${_name} ${_test}) target_link_libraries(${_name} kimap ${QT_QTTEST_LIBRARY} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTNETWORK_LIBRARY} ) ENDMACRO(ADD_IMAPLIB_TEST) ### tests ADD_IMAPLIB_TEST(loginjobtest.cpp) ADD_IMAPLIB_TEST(logoutjobtest.cpp) ADD_IMAPLIB_TEST(capabilitiesjobtest.cpp) ADD_IMAPLIB_TEST(selectjobtest.cpp) ADD_IMAPLIB_TEST(createjobtest.cpp) ADD_IMAPLIB_TEST(deletejobtest.cpp) ADD_IMAPLIB_TEST(renamejobtest.cpp) ADD_IMAPLIB_TEST(subscribejobtest.cpp) ADD_IMAPLIB_TEST(unsubscribejobtest.cpp) ADD_IMAPLIB_TEST(listjobtest.cpp) +ADD_IMAPLIB_TEST(storejobtest.cpp) ########### automated tests ############### KIMAP_UNIT_TESTS( testrfccodecs testsession ) ########### manual tests ############### KIMAP_EXECUTABLE_TESTS( testimapserver ) diff --git a/kimap/tests/storejobtest.cpp b/kimap/tests/storejobtest.cpp new file mode 100644 index 000000000..8befcaa3b --- /dev/null +++ b/kimap/tests/storejobtest.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2009 Kevin Ottens + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include + +#include "fakeserver.h" +#include "kimap/session.h" +#include "kimap/storejob.h" + +#include +#include +#include + +Q_DECLARE_METATYPE(QList) + +class StoreJobTest: public QObject { + Q_OBJECT + +private Q_SLOTS: + +void testStore_data() { + QTest::addColumn( "uidBased" ); + QTest::addColumn( "id" ); + QTest::addColumn( "uid" ); + QTest::addColumn< QList >( "flags" ); + QTest::addColumn( "response" ); + + QStringList response; + response << "* 3 FETCH (FLAGS (\\Seen \\Foo) UID 1096)"; + response << "A000001 OK STORE completed"; + + QTest::newRow( "not uid based" ) << false << qint64(3) << qint64(1096) + << ( QList() << "\\Seen" << "\\Foo" ) + << response; + + QTest::newRow( "uid based" ) << true << qint64(3) << qint64(1096) + << ( QList() << "\\Seen" << "\\Foo" ) + << response; +} + +void testStore() +{ + FakeServer fakeServer; + fakeServer.start(); + KIMAP::Session session("127.0.0.1", 5989); + QFETCH( bool, uidBased ); + QFETCH( qint64, id ); + QFETCH( qint64, uid ); + QFETCH( QList, flags ); + QFETCH( QStringList, response ); + + fakeServer.setResponse( response ); + + KIMAP::StoreJob *job = new KIMAP::StoreJob(&session); + job->setUidBased( uidBased ); + job->setSequenceSet( KIMAP::ImapSet( uidBased ? uid : id ) ); + job->setFlags( flags ); + job->setMode( KIMAP::StoreJob::SetFlags ); + bool result = job->exec(); + QVERIFY(result); + if ( uidBased ) { + QVERIFY( job->resultingFlags().contains( uid ) ); + } else { + QVERIFY( job->resultingFlags().contains( id ) ); + } + + fakeServer.quit(); +} + + +}; + +QTEST_KDEMAIN( StoreJobTest, NoGUI ) + +#include "storejobtest.moc"