diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt index 3b73e988d..2a001edbd 100644 --- a/akonadi/CMakeLists.txt +++ b/akonadi/CMakeLists.txt @@ -1,266 +1,268 @@ project(akonadi-kde) add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5250 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) if(CMAKE_COMPILE_GCOV) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") endif(CMAKE_COMPILE_GCOV) if (KDE4_BUILD_TESTS) # only with this macro the AKONADI_TESTS_EXPORT macro will do something add_definitions(-DCOMPILING_TESTS) add_subdirectory( tests ) endif (KDE4_BUILD_TESTS) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory( kabc ) add_subdirectory( kmime ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR}/akonadi/private ) # libakonadi-kde set( akonadikde_LIB_SRC entity.cpp # keep it at top to not break enable-final agentbase.cpp agentfilterproxymodel.cpp agentinstance.cpp agentinstancecreatejob.cpp agentinstancemodel.cpp agentinstancewidget.cpp agentmanager.cpp agenttype.cpp agenttypemodel.cpp agenttypewidget.cpp agenttypedialog.cpp attribute.cpp attributefactory.cpp cachepolicy.cpp cachepolicypage.cpp changerecorder.cpp collection.cpp collectioncopyjob.cpp collectioncreatejob.cpp collectiondeletejob.cpp collectiondialog.cpp collectionfilterproxymodel.cpp collectiongeneralpropertiespage.cpp collectionfetchjob.cpp collectionfetchscope.cpp collectionmodel.cpp collectionmodel_p.cpp collectionmodifyjob.cpp collectionmovejob.cpp collectionpathresolver.cpp collectionpropertiesdialog.cpp collectionpropertiespage.cpp collectionrequester.cpp collectionrightsattribute.cpp collectionselectjob.cpp collectionstatistics.cpp collectionstatisticsdelegate.cpp collectionstatisticsjob.cpp collectionstatisticsmodel.cpp collectionsync.cpp collectionview.cpp control.cpp descendantsproxymodel.cpp entitydisplayattribute.cpp + entityhiddenattribute.cpp entitytreemodel.cpp entitytreemodel_p.cpp entityfilterproxymodel.cpp entitytreeviewstatesaver.cpp erroroverlay.cpp exception.cpp expungejob.cpp favoritecollectionsmodel.cpp firstrun.cpp flatcollectionproxymodel.cpp item.cpp itemcreatejob.cpp itemcopyjob.cpp itemdeletejob.cpp itemfetchjob.cpp itemfetchscope.cpp itemmodel.cpp itemmonitor.cpp itemmovejob.cpp itemserializer.cpp itemserializerplugin.cpp itemmodifyjob.cpp itemsync.cpp itemview.cpp job.cpp linkjob.cpp filteractionjob.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp pastehelper.cpp preprocessorbase.cpp protocolhelper.cpp resourcebase.cpp resourcescheduler.cpp resourceselectjob.cpp searchcreatejob.cpp selectionproxymodel.cpp selftestdialog.cpp session.cpp servermanager.cpp standardactionmanager.cpp statisticsproxymodel.cpp statisticstooltipproxymodel.cpp subscriptionjob.cpp subscriptionchangeproxymodel.cpp subscriptiondialog.cpp subscriptionmodel.cpp transactionjobs.cpp transactionsequence.cpp transportresourcebase.cpp unlinkjob.cpp # Temporary until ported to Qt-plugin framework pluginloader.cpp ) # DBus interfaces and adaptors set(akonadi_xml ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.NotificationManager.xml) set_source_files_properties(${akonadi_xml} PROPERTIES INCLUDE "notificationmessage_p.h") qt4_add_dbus_interface( akonadikde_LIB_SRC ${akonadi_xml} notificationmanagerinterface ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.AgentManager.xml ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Tracer.xml ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Resource.xml resourcebase.h Akonadi::ResourceBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Preprocessor.xml preprocessorbase.h Akonadi::PreprocessorBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Status.xml agentbase.h Akonadi::AgentBase ) # TODO move this to kdesupport/akonadi/interfaces qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Resource.Transport.xml transportresourcebase_p.h Akonadi::TransportResourceBasePrivate ) # TODO merge with Akonadi.Control in kdesupport/akonadi/interfaces qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) #qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) kde4_add_ui_files( akonadikde_LIB_SRC cachepolicypage.ui collectiongeneralpropertiespage.ui subscriptiondialog.ui controlprogressindicator.ui selftestdialog.ui ) kde4_add_library( akonadi-kde SHARED ${akonadikde_LIB_SRC} ) macro_ensure_version( "4.2.0" ${KDE_VERSION} KDE_IS_AT_LEAST_42 ) target_link_libraries( akonadi-kde ${KDE4_SOLID_LIBS} ${QT_QTNETWORK_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTSQL_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${AKONADI_COMMON_LIBRARIES} ) set( AKONADI_KDE_DEPS ${KDE4_KDEUI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ) if(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde LINK_INTERFACE_LIBRARIES ${AKONADI_KDE_DEPS}) else(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde ${AKONADI_KDE_DEPS}) endif(${KDE_IS_AT_LEAST_42}) set_target_properties( akonadi-kde PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS akonadi-kde EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( FILES akonadi_export.h agentbase.h agentfilterproxymodel.h agentinstance.h agentinstancecreatejob.h agentinstancemodel.h agentinstancewidget.h agentmanager.h agenttype.h agenttypemodel.h agenttypewidget.h agenttypedialog.h attribute.h attributefactory.h cachepolicy.h changerecorder.h collection.h collectioncopyjob.h collectioncreatejob.h collectiondeletejob.h collectiondialog.h collectionfilterproxymodel.h collectionfetchjob.h collectionfetchscope.h collectionmodel.h collectionmodifyjob.h collectionpropertiesdialog.h collectionpropertiespage.h collectionrequester.h collectionstatisticsdelegate.h collectionstatisticsmodel.h collectionstatistics.h collectionstatisticsjob.h collectionview.h control.h descendantsproxymodel.h entity.h entitydisplayattribute.h + entityhiddenattribute.h entitytreemodel.h entityfilterproxymodel.h entitytreeviewstatesaver.h exception.h favoritecollectionsmodel.h item.h itemcreatejob.h itemcopyjob.h itemdeletejob.h itemfetchjob.h itemfetchscope.h itemmodel.h itemmodifyjob.h itemmonitor.h itemmovejob.h itempayloadinternals_p.h itemserializerplugin.h itemsync.h itemview.h job.h linkjob.h filteractionjob.h mimetypechecker.h monitor.h qtest_akonadi.h preprocessorbase.h resourcebase.h searchcreatejob.h selectionproxymodel.h session.h servermanager.h standardactionmanager.h statisticsproxymodel.h statisticstooltipproxymodel.h transactionjobs.h transactionsequence.h transportresourcebase.h unlinkjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi COMPONENT Devel ) install( FILES collectionpathresolver_p.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/private COMPONENT Devel ) install( FILES kcfg2dbus.xsl DESTINATION ${DATA_INSTALL_DIR}/akonadi-kde ) diff --git a/akonadi/attributefactory.cpp b/akonadi/attributefactory.cpp index 4252147bd..c075fa424 100644 --- a/akonadi/attributefactory.cpp +++ b/akonadi/attributefactory.cpp @@ -1,115 +1,117 @@ /* Copyright (c) 2007 - 2008 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 "attributefactory.h" #include "collectionrightsattribute_p.h" #include "entitydisplayattribute.h" +#include "entityhiddenattribute.h" #include #include using namespace Akonadi; /** * @internal */ class DefaultAttribute : public Attribute { public: explicit DefaultAttribute( const QByteArray &type, const QByteArray &value = QByteArray() ) : mType( type ), mValue( value ) {} QByteArray type() const { return mType; } Attribute* clone() const { return new DefaultAttribute( mType, mValue ); } QByteArray serialized() const { return mValue; } void deserialize( const QByteArray &data ) { mValue = data; } private: QByteArray mType, mValue; }; /** * @internal */ class AttributeFactory::Private { public: QHash attributes; }; /** * @internal */ class StaticAttributeFactory : public AttributeFactory { public: StaticAttributeFactory() : AttributeFactory(), initialized( false ) {} void init() { if ( initialized ) return; initialized = true; // Register built-in attributes AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); + AttributeFactory::registerAttribute(); } bool initialized; }; K_GLOBAL_STATIC( StaticAttributeFactory, s_attributeInstance ) AttributeFactory* AttributeFactory::self() { s_attributeInstance->init(); return s_attributeInstance; } AttributeFactory::AttributeFactory() : d( new Private ) { } AttributeFactory::~ AttributeFactory() { qDeleteAll( d->attributes ); delete d; } void AttributeFactory::registerAttribute(Attribute *attr) { Q_ASSERT( !d->attributes.contains( attr->type() ) ); d->attributes.insert( attr->type(), attr ); } Attribute* AttributeFactory::createAttribute(const QByteArray &type) { Attribute* attr = self()->d->attributes.value( type ); if ( attr ) return attr->clone(); return new DefaultAttribute( type ); } diff --git a/akonadi/collectionfetchjob.cpp b/akonadi/collectionfetchjob.cpp index bf8958ef2..972644a0b 100644 --- a/akonadi/collectionfetchjob.cpp +++ b/akonadi/collectionfetchjob.cpp @@ -1,245 +1,262 @@ /* 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 "collectionfetchscope.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate( CollectionFetchJob *parent ) : JobPrivate( parent ) { } Q_DECLARE_PUBLIC( CollectionFetchJob ) CollectionFetchJob::Type mType; Collection mBase; Collection::List mBaseList; Collection::List mCollections; CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer *mEmitTimer; 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->mScope.includeUnubscribed() ) 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 ); } + QList filter; if ( !d->mScope.resource().isEmpty() ) { - command += "RESOURCE \""; - command += d->mScope.resource().toUtf8(); - command += '"'; + filter.append( "RESOURCE" ); + filter.append( d->mScope.resource().toUtf8() ); } if ( !d->mScope.contentMimeTypes().isEmpty() ) { - command += " MIMETYPE ("; + filter.append( "MIMETYPE" ); QList mts; foreach ( const QString &mt, d->mScope.contentMimeTypes() ) mts.append( mt.toUtf8() ); - command += ImapParser::join( mts, " " ); - command += ')'; + filter.append( "(" + ImapParser::join( mts, " " ) + ")" ); } + QList options; if ( d->mScope.includeStatistics() ) { - command += ") (STATISTICS true"; + options.append( "STATISTICS" ); + options.append( "true" ); + } + if ( d->mScope.ancestorRetrieval() != CollectionFetchScope::None ) { + options.append( "ANCESTORS" ); + switch ( d->mScope.ancestorRetrieval() ) { + case CollectionFetchScope::None: + options.append( "0" ); + break; + case CollectionFetchScope::Parent: + options.append( "1" ); + break; + case CollectionFetchScope::All: + options.append( "INF" ); + break; + default: + Q_ASSERT( false ); + } } - command += ")\n"; + command += ImapParser::join( filter, " " ) + ") (" + ImapParser::join( options, " " ) + ")\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() << "Unhandled server response" << tag << data; } void CollectionFetchJob::setResource(const QString & resource) { Q_D( CollectionFetchJob ); d->mScope.setResource( resource ); } 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->mScope.setIncludeUnsubscribed( include ); } void CollectionFetchJob::includeStatistics(bool include) { Q_D( CollectionFetchJob ); d->mScope.setIncludeStatistics( include ); } void CollectionFetchJob::setFetchScope( const CollectionFetchScope &scope ) { Q_D( CollectionFetchJob ); d->mScope = scope; } CollectionFetchScope& CollectionFetchJob::fetchScope() { Q_D( CollectionFetchJob ); return d->mScope; } #include "collectionfetchjob.moc" diff --git a/akonadi/collectionfetchjob.h b/akonadi/collectionfetchjob.h index 9e99635d7..c0301f660 100644 --- a/akonadi/collectionfetchjob.h +++ b/akonadi/collectionfetchjob.h @@ -1,180 +1,177 @@ /* 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 CollectionFetchScope; 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(); - * } - * } + * // fetching all collections containing emails recursively, starting at the root collection + * CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this ); + * job->fetchScope().setContentMimeTypes( QStringList() << "message/rfc822" ); + * connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List), this, SLOT(myCollectionsReceived(Akonadi::Collection::List)) ); + * connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*)) ); * * @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. * @deprecated Use CollectionFetchScope instead. */ KDE_DEPRECATED void setResource( const QString &resource ); /** * Include also unsubscribed collections. * @deprecated Use CollectionFetchScope instead. */ KDE_DEPRECATED void includeUnsubscribed( bool include = true ); /** * Include also statistics about the collections. * * @since 4.3 * @deprecated Use CollectionFetchScope instead. */ KDE_DEPRECATED void includeStatistics( bool include = true ); /** * Sets the collection fetch scope. * * The CollectionFetchScope controls how much of a collection's data is fetched * from the server as well as filter to select which collections to fetch. * * @param fetchScope The new scope for collection fetch operations. * * @see fetchScope() * @since 4.4 */ void setFetchScope( const CollectionFetchScope &fetchScope ); /** * Returns the collection fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the CollectionFetchScope documentation * for an example. * * @return a reference to the current collection fetch scope * * @see setFetchScope() for replacing the current collection fetch scope * @since 4.4 */ CollectionFetchScope &fetchScope(); 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/collectionsync.cpp b/akonadi/collectionsync.cpp index 62a3f05d9..7b2de0589 100644 --- a/akonadi/collectionsync.cpp +++ b/akonadi/collectionsync.cpp @@ -1,532 +1,535 @@ /* Copyright (c) 2007, 2009 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 "collectionsync_p.h" #include "collection.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "collectionfetchscope.h" #include #include using namespace Akonadi; struct RemoteNode; /** LocalNode is used to build a tree structure of all our locally existing collections. */ struct LocalNode { LocalNode( const Collection &col ) : collection( col ), - parentNode( 0 ), processed( false ) {} ~LocalNode() { qDeleteAll( childNodes ); qDeleteAll( pendingRemoteNodes ); } Collection collection; - LocalNode *parentNode; // ### really needed? QList childNodes; QHash childRidMap; /** When using hierarchical RIDs we attach a list of not yet processable remote nodes to the closest already existing local ancestor node. They will be re-evaluated once a new child node is added. */ QList pendingRemoteNodes; bool processed; }; Q_DECLARE_METATYPE( LocalNode* ) static const char LOCAL_NODE[] = "LocalNode"; /** RemoteNode is used as a container for remote collections which typically don't have a UID set and thus cannot easily be compared or put into maps etc. */ struct RemoteNode { RemoteNode( const Collection &col ) : collection( col ) {} Collection collection; }; Q_DECLARE_METATYPE( RemoteNode* ) static const char REMOTE_NODE[] = "RemoteNode"; /** * @internal */ class CollectionSync::Private { public: Private( CollectionSync *parent ) : q( parent ), pendingJobs( 0 ), incremental( false ), streaming( false ), hierarchicalRIDs( false ), localListDone( false ), deliveryDone( false ) { localRoot = new LocalNode( Collection::root() ); + localRoot->processed = true; // never try to delete that one localUidMap.insert( localRoot->collection.id(), localRoot ); if ( !hierarchicalRIDs ) localRidMap.insert( QString(), localRoot ); } ~Private() { delete localRoot; } /** Create a local node from the given local collection and integrate it into the local tree structure. */ LocalNode* createLocalNode( const Collection &col ) { LocalNode *node = new LocalNode( col ); Q_ASSERT( !localUidMap.contains( col.id() ) ); localUidMap.insert( node->collection.id(), node ); if ( !hierarchicalRIDs ) localRidMap.insert( node->collection.remoteId(), node ); // add already existing children if ( localPendingCollections.contains( col.id() ) ) { QList childIds = localPendingCollections.take( col.id() ); foreach ( Collection::Id childId, childIds ) { Q_ASSERT( localUidMap.contains( childId ) ); LocalNode *childNode = localUidMap.value( childId ); node->childNodes.append( childNode ); node->childRidMap.insert( childNode->collection.remoteId(), childNode ); } } // set our parent and add ourselves as child if ( localUidMap.contains( col.parentCollection().id() ) ) { - node->parentNode = localUidMap.value( col.parentCollection().id() ); - node->parentNode->childNodes.append( node ); + LocalNode* parentNode = localUidMap.value( col.parentCollection().id() ); + parentNode->childNodes.append( node ); + parentNode->childRidMap.insert( node->collection.remoteId(), node ); } else { localPendingCollections[ col.parentCollection().id() ].append( col.id() ); } return node; } /** Same as createLocalNode() for remote collections. */ void createRemoteNode( const Collection &col ) { if ( col.remoteId().isEmpty() ) { kWarning() << "Collection '" << col.name() << "' does not have a remote identifier - skipping"; return; } RemoteNode *node = new RemoteNode( col ); localRoot->pendingRemoteNodes.append( node ); } /** Create local nodes as we receive the local listing from the Akonadi server. */ void localCollectionsReceived( const Akonadi::Collection::List &localCols ) { foreach ( const Collection &c, localCols ) createLocalNode( c ); } /** Once the local collection listing finished we can continue with the interesting stuff. */ void localCollectionFetchResult( KJob *job ) { if ( job->error() ) return; // handled by the base class // safety check: the local tree has to be connected if ( !localPendingCollections.isEmpty() ) { q->setError( Unknown ); q->setErrorText( QLatin1String( "Inconsistent local collection tree detected! OMG, what have you done to my database?!?!" ) ); q->emitResult(); return; } localListDone = true; execute(); } /** Find the local node that matches the given remote collection, returns 0 if that doesn't exist (yet). */ LocalNode* findMatchingLocalNode( const Collection &collection ) { if ( !hierarchicalRIDs ) { if ( localRidMap.contains( collection.remoteId() ) ) return localRidMap.value( collection.remoteId() ); return 0; } else { + if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() ) + return localRoot; LocalNode *localParent = 0; if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) { kWarning() << "Remote collection without valid parent found: " << collection; return 0; } - if ( collection.parentCollection() == Collection::root() ) + if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() ) localParent = localRoot; else localParent = findMatchingLocalNode( collection.parentCollection() ); if ( localParent && localParent->childRidMap.contains( collection.remoteId() ) ) return localParent->childRidMap.value( collection.remoteId() ); return 0; } } /** Find the local node that is the nearest ancestor of the given remote collection (when using hierarchical RIDs only, otherwise it's always the local root node). Never returns 0. */ LocalNode* findBestLocalAncestor( const Collection &collection, bool *exactMatch = 0 ) { if ( !hierarchicalRIDs ) return localRoot; if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) { kWarning() << "Remote collection without valid parent found: " << collection; return 0; } if ( collection.parentCollection() == Collection::root() ) { if ( exactMatch ) *exactMatch = true; return localRoot; } bool parentIsExact = false; LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact ); if ( !parentIsExact ) { if ( exactMatch ) *exactMatch = false; return localParent; } if ( localParent->childRidMap.contains( collection.remoteId() ) ) { if ( exactMatch ) *exactMatch = true; return localParent->childRidMap.value( collection.remoteId() ); } if ( exactMatch ) *exactMatch = false; return localParent; } /** Checks the pending remote nodes attached to the given local root node to see if any of them can be processed by now. If not, they are moved to the closest ancestor available. */ void processPendingRemoteNodes( LocalNode *localRoot ) { QList pendingRemoteNodes( localRoot->pendingRemoteNodes ); localRoot->pendingRemoteNodes.clear(); QHash > pendingCreations; foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) { // step 1: see if we have a matching local node already LocalNode *localNode = findMatchingLocalNode( remoteNode->collection ); if ( localNode ) { Q_ASSERT( !localNode->processed ); // TODO: moving when using global RIDs updateLocalCollection( localNode, remoteNode ); continue; } // step 2: check if we have the parent at least, then we can create it localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() ); if ( localNode ) { pendingCreations[localNode].append( remoteNode ); continue; } // step 3: find the best matching ancestor and enqueue it for later processing localNode = findBestLocalAncestor( remoteNode->collection ); if ( !localNode ) { q->setError( Unknown ); q->setErrorText( QLatin1String( "Remote collection without root-terminated ancestor chain provided, fix your resource dude!" ) ); q->emitResult(); return; } localNode->pendingRemoteNodes.append( remoteNode ); } // process the now possible collection creations for ( QHash >::const_iterator it = pendingCreations.constBegin(); it != pendingCreations.constEnd(); ++it ) { createLocalCollections( it.key(), it.value() ); } } /** Performs a local update for the given node pair. */ void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode ) { ++pendingJobs; Collection upd( remoteNode->collection ); upd.setId( localNode->collection.id() ); CollectionModifyJob *mod = new CollectionModifyJob( upd, q ); mod->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) ); connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) ); localNode->processed = true; } void updateLocalCollectionResult( KJob* job ) { --pendingJobs; if ( job->error() ) return; // handled by the base class RemoteNode* remoteNode = job->property( REMOTE_NODE ).value(); delete remoteNode; checkDone(); } /** Creates local folders for the given local parent and remote nodes. @todo group CollectionCreateJobs into a single one once it supports that */ void createLocalCollections( LocalNode* localParent, QList remoteNodes ) { foreach ( RemoteNode *remoteNode, remoteNodes ) { ++pendingJobs; Collection col( remoteNode->collection ); col.setParentCollection( localParent->collection ); CollectionCreateJob *create = new CollectionCreateJob( col, q ); create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) ); create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) ); connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) ); } } void createLocalCollectionResult( KJob* job ) { --pendingJobs; if ( job->error() ) return; // handled by the base class const Collection newLocal = static_cast( job )->collection(); LocalNode* localNode = createLocalNode( newLocal ); localNode->processed = true; LocalNode* localParent = job->property( LOCAL_NODE ).value(); Q_ASSERT( localParent->childNodes.contains( localNode ) ); RemoteNode* remoteNode = job->property( REMOTE_NODE ).value(); delete remoteNode; processPendingRemoteNodes( localParent ); if ( !hierarchicalRIDs ) processPendingRemoteNodes( localRoot ); checkDone(); } /** Find all local nodes that are not marked as processed. */ Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) { Collection::List rv; if ( !localNode->processed ) { rv.append( localNode->collection ); return rv; } foreach ( LocalNode *child, localNode->childNodes ) rv.append( findUnprocessedLocalCollections( child ) ); return rv; } /** Deletes unprocessed local nodes, in non-incremental mode. */ void deleteUnprocessedLocalNodes() { if ( incremental ) return; Collection::List cols = findUnprocessedLocalCollections( localRoot ); + deleteLocalCollections( cols ); } /** Deletes the given collection list. @todo optimite delete job to support batch operations */ void deleteLocalCollections( const Collection::List &cols ) { foreach ( const Collection &col, cols ) { ++pendingJobs; CollectionDeleteJob *job = new CollectionDeleteJob( col, q ); connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) ); } } void deleteLocalCollectionsResult( KJob *job ) { if ( job->error() ) return; // handled by the base class --pendingJobs; checkDone(); } /** Process what's currently available. */ void execute() { if ( !localListDone ) return; processPendingRemoteNodes( localRoot ); if ( !incremental && deliveryDone ) deleteUnprocessedLocalNodes(); if ( !hierarchicalRIDs ) { deleteLocalCollections( removedRemoteCollections ); } else { Collection::List localCols; foreach ( const Collection &c, removedRemoteCollections ) { LocalNode *node = findMatchingLocalNode( c ); if ( node ) localCols.append( node->collection ); } deleteLocalCollections( localCols ); } removedRemoteCollections.clear(); checkDone(); } /** Finds pending remote nodes, which at the end of the day should be an empty set. */ QList findPendingRemoteNodes( LocalNode *localNode ) { QList rv; rv.append( localNode->pendingRemoteNodes ); foreach ( LocalNode *child, localNode->childNodes ) rv.append( findPendingRemoteNodes( child ) ); return rv; } /** Are we there yet?? @todo progress reporting */ void checkDone() { // still running jobs or not fully delivered local/remote state if ( !deliveryDone || pendingJobs > 0 || !localListDone ) return; // safety check: there must be no pending remote nodes anymore QList orphans = findPendingRemoteNodes( localRoot ); if ( !orphans.isEmpty() ) { q->setError( Unknown ); q->setErrorText( QLatin1String( "Found unresolved orphan collections" ) ); foreach ( RemoteNode* orphan, orphans ) kDebug() << "found orphan collection:" << orphan->collection; } q->commit(); } CollectionSync *q; QString resourceId; int pendingJobs; LocalNode* localRoot; QHash localUidMap; QHash localRidMap; // temporary during build-up of the local node tree, must be empty afterwards QHash > localPendingCollections; // removed remote collections in incremental mode Collection::List removedRemoteCollections; bool incremental; bool streaming; bool hierarchicalRIDs; bool localListDone; bool deliveryDone; }; CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) : TransactionSequence( parent ), d( new Private( this ) ) { d->resourceId = resourceId; } CollectionSync::~CollectionSync() { delete d; } void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections) { foreach ( const Collection &c, remoteCollections ) d->createRemoteNode( c ); if ( !d->streaming ) d->deliveryDone = true; d->execute(); } void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections) { d->incremental = true; foreach ( const Collection &c, changedCollections ) d->createRemoteNode( c ); d->removedRemoteCollections += removedCollections; if ( !d->streaming ) d->deliveryDone = true; d->execute(); } void CollectionSync::doStart() { CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this ); job->fetchScope().setResource( d->resourceId ); connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(localCollectionsReceived(Akonadi::Collection::List)) ); connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) ); } void CollectionSync::setStreamingEnabled( bool streaming ) { d->streaming = streaming; } void CollectionSync::retrievalDone() { d->deliveryDone = true; d->execute(); } void CollectionSync::setHierarchicalRemoteIds( bool hierarchical ) { d->hierarchicalRIDs = hierarchical; } #include "collectionsync_p.moc" diff --git a/akonadi/descendantsproxymodel.h b/akonadi/descendantsproxymodel.h index f72524b2d..d18f06cda 100644 --- a/akonadi/descendantsproxymodel.h +++ b/akonadi/descendantsproxymodel.h @@ -1,82 +1,82 @@ /* Copyright (c) 2009 Stephen Kelly 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_DESCENDANTSPROXYMODEL_H #define AKONADI_DESCENDANTSPROXYMODEL_H #include "akonadi_export.h" #include namespace Akonadi { class DescendantsProxyModelPrivate; /** * @short A proxy model that flattens a tree-based model to a list model. * * @author Stephen Kelly * @since 4.4 */ class AKONADI_EXPORT DescendantsProxyModel : public KDescendantsProxyModel { Q_OBJECT public: /** * Creates a new descendants proxy model. * * @param parent The parent object. */ DescendantsProxyModel( QObject *parent = 0 ); /** * Destroys the descendants proxy model. */ virtual ~DescendantsProxyModel(); /** * Sets the header @p set that shall be used by the proxy model. * * \s EntityTreeModel::HeaderGroup */ void setHeaderSet( int set ); /** * Returns the header set used by the proxy model. */ int headerSet() const; // QAbstractProxyModel does not proxy all methods... virtual bool dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ); virtual QMimeData* mimeData( const QModelIndexList & indexes ) const; virtual QStringList mimeTypes() const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; private: //@cond PRIVATE Q_DECLARE_PRIVATE(DescendantsProxyModel) - DescendantsProxyModelPrivate *d_ptr; + DescendantsProxyModelPrivate * const d_ptr; //@endcond }; } #endif diff --git a/akonadi/entityfilterproxymodel.cpp b/akonadi/entityfilterproxymodel.cpp index 5f5d02769..31b3d4315 100644 --- a/akonadi/entityfilterproxymodel.cpp +++ b/akonadi/entityfilterproxymodel.cpp @@ -1,271 +1,279 @@ /* Copyright (c) 2007 Bruno Virlet Copyright (c) 2009 Stephen Kelly 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 "entityfilterproxymodel.h" #include "entitytreemodel.h" #include #include #include #include using namespace Akonadi; namespace Akonadi { /** * @internal */ class EntityFilterProxyModelPrivate { public: EntityFilterProxyModelPrivate( EntityFilterProxyModel *parent ) : q_ptr( parent ), m_headerSet(0) { } QStringList includedMimeTypes; QStringList excludedMimeTypes; QPersistentModelIndex m_rootIndex; int m_headerSet; Q_DECLARE_PUBLIC(EntityFilterProxyModel) EntityFilterProxyModel *q_ptr; }; } EntityFilterProxyModel::EntityFilterProxyModel( QObject *parent ) : QSortFilterProxyModel( parent ), d_ptr( new EntityFilterProxyModelPrivate( this ) ) { // TODO: Override setSourceModel and do this there? setSupportedDragActions( Qt::CopyAction | Qt::MoveAction ); } EntityFilterProxyModel::~EntityFilterProxyModel() { delete d_ptr; } void EntityFilterProxyModel::addMimeTypeInclusionFilters(const QStringList &typeList) { Q_D(EntityFilterProxyModel); d->includedMimeTypes << typeList; invalidateFilter(); } void EntityFilterProxyModel::addMimeTypeExclusionFilters(const QStringList &typeList) { Q_D(EntityFilterProxyModel); d->excludedMimeTypes << typeList; invalidateFilter(); } void EntityFilterProxyModel::addMimeTypeInclusionFilter(const QString &type) { Q_D(EntityFilterProxyModel); d->includedMimeTypes << type; invalidateFilter(); } void EntityFilterProxyModel::addMimeTypeExclusionFilter(const QString &type) { Q_D(EntityFilterProxyModel); d->excludedMimeTypes << type; invalidateFilter(); } bool EntityFilterProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent) const { Q_D(const EntityFilterProxyModel); // All rows that are not below m_rootIndex are unfiltered. bool found = false; const bool rootIsValid = d->m_rootIndex.isValid(); QModelIndex _parent = sourceParent; while (true) { if (_parent == d->m_rootIndex) { found = true; break; } _parent = _parent.parent(); if (!_parent.isValid() && rootIsValid) { break; } } if (!found) { return true; } const QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent); const QString rowMimetype = idx.data( EntityTreeModel::MimeTypeRole ).toString(); if ( d->excludedMimeTypes.contains( rowMimetype ) ) return false; if ( d->includedMimeTypes.isEmpty() || d->includedMimeTypes.contains( rowMimetype ) ) return true; return false; } QStringList EntityFilterProxyModel::mimeTypeInclusionFilters() const { Q_D(const EntityFilterProxyModel); return d->includedMimeTypes; } QStringList EntityFilterProxyModel::mimeTypeExclusionFilters() const { Q_D(const EntityFilterProxyModel); return d->excludedMimeTypes; } void EntityFilterProxyModel::clearFilters() { Q_D(EntityFilterProxyModel); d->includedMimeTypes.clear(); d->excludedMimeTypes.clear(); invalidateFilter(); } void EntityFilterProxyModel::setRootIndex(const QModelIndex &srcIndex) { Q_D(EntityFilterProxyModel); d->m_rootIndex = srcIndex; reset(); } void EntityFilterProxyModel::setHeaderSet(int set) { Q_D(EntityFilterProxyModel); d->m_headerSet = set; } QVariant EntityFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role ) const { Q_D(const EntityFilterProxyModel); role += (EntityTreeModel::TerminalUserRole * d->m_headerSet); return sourceModel()->headerData(section, orientation, role); } bool EntityFilterProxyModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) { Q_ASSERT(sourceModel()); const QModelIndex sourceParent = mapToSource(parent); return sourceModel()->dropMimeData(data, action, row, column, sourceParent); } QMimeData* EntityFilterProxyModel::mimeData( const QModelIndexList & indexes ) const { Q_ASSERT(sourceModel()); QModelIndexList sourceIndexes; foreach(const QModelIndex& index, indexes) sourceIndexes << mapToSource(index); return sourceModel()->mimeData(sourceIndexes); } QStringList EntityFilterProxyModel::mimeTypes() const { Q_ASSERT(sourceModel()); return sourceModel()->mimeTypes(); } QModelIndexList EntityFilterProxyModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const { if (EntityTreeModel::AmazingCompletionRole != role) return QSortFilterProxyModel::match(start, role, value, hits, flags); // We match everything in the source model because sorting will change what we should show. const int allHits = -1; QModelIndexList proxyList; QMap proxyMap; QModelIndexList sourceList = sourceModel()->match(mapToSource(start), role, value, allHits, flags); QModelIndexList::const_iterator it; const QModelIndexList::const_iterator begin = sourceList.constBegin(); const QModelIndexList::const_iterator end = sourceList.constEnd(); QModelIndex proxyIndex; for (it = begin; it != end; ++it) { proxyIndex = mapFromSource(*it); // Any filtered indexes will be invalid when mapped. if (!proxyIndex.isValid()) continue; // Inserting in a QMap gives us sorting by key for free. proxyMap.insert(proxyIndex.row(), proxyIndex); } if (hits == -1) return proxyMap.values(); return proxyMap.values().mid(0, hits); } int EntityFilterProxyModel::columnCount(const QModelIndex &parent) const { Q_D(const EntityFilterProxyModel); QVariant var = sourceModel()->data(parent, EntityTreeModel::ColumnCountRole + (EntityTreeModel::TerminalUserRole * d->m_headerSet)); if( !var.isValid() ) return 0; return var.toInt(); } bool EntityFilterProxyModel::hasChildren(const QModelIndex &parent) const { if ( !parent.isValid() ) return sourceModel()->hasChildren(parent); Q_D(const EntityFilterProxyModel); if ( EntityTreeModel::ItemListHeaders == d->m_headerSet) return false; if ( EntityTreeModel::CollectionTreeHeaders == d->m_headerSet ) { QModelIndex childIndex = parent.child( 0, 0 ); while ( childIndex.isValid() ) { Collection col = childIndex.data( EntityTreeModel::CollectionRole ).value(); if (col.isValid()) return true; childIndex = childIndex.sibling( childIndex.row() + 1, childIndex.column() ); } } return false; } +bool EntityFilterProxyModel::canFetchMore( const QModelIndex &parent ) const +{ + Q_D(const EntityFilterProxyModel); + if ( EntityTreeModel::CollectionTreeHeaders == d->m_headerSet ) + return false; + return QSortFilterProxyModel::canFetchMore(parent); +} + #include "entityfilterproxymodel.moc" diff --git a/akonadi/entityfilterproxymodel.h b/akonadi/entityfilterproxymodel.h index 3416dc422..e9e00f5ee 100644 --- a/akonadi/entityfilterproxymodel.h +++ b/akonadi/entityfilterproxymodel.h @@ -1,160 +1,162 @@ /* Copyright (c) 2007 Bruno Virlet Copyright (c) 2009 Stephen Kelly 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_ENTITYFILTERPROXYMODEL_H #define AKONADI_ENTITYFILTERPROXYMODEL_H #include "akonadi_export.h" #include namespace Akonadi { class EntityFilterProxyModelPrivate; /** * @short A proxy model that filters entities by mime type. * * This class can be used on top of an EntityTreeModel to exclude entities by mimetype * or to include only certain mimetypes. * * @code * * Akonadi::EntityTreeModel *model = new Akonadi::EntityTreeModel( this ); * * Akonadi::EntityFilterProxyModel *proxy = new Akonadi::EntityFilterProxyModel(); * proxy->addMimeTypeInclusionFilter( "message/rfc822" ); * proxy->setSourceModel( model ); * * Akonadi::EntityTreeView *view = new Akonadi::EntityTreeView( this ); * view->setModel( proxy ); * * @endcode * * @li If a mimetype is in both the exclusion list and the inclusion list, it is excluded. * @li If the mimeTypeInclusionFilter is empty, all mimetypes are * accepted (except if they are in the exclusion filter of course). * * * @author Bruno Virlet * @author Stephen Kelly * @since 4.4 */ class AKONADI_EXPORT EntityFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: /** * Creates a new proxy filter model. * * @param parent The parent object. */ explicit EntityFilterProxyModel( QObject *parent = 0 ); /** * Destroys the proxy filter model. */ virtual ~EntityFilterProxyModel(); /** * Add mime types to be shown by the filter. * * @param mimeTypes A list of mime types to be included. */ void addMimeTypeInclusionFilters( const QStringList &mimeTypes ); /** * Add mimetypes to filter out * * @param mimeTypes A list to exclude from the model. */ void addMimeTypeExclusionFilters( const QStringList &mimeTypes ); /** * Add mime type to be shown by the filter. * * @param mimeType A mime type to be shown. */ void addMimeTypeInclusionFilter( const QString &mimeType ); /** * Add mime type to be excluded by the filter. * * @param mimeType A mime type to be excluded. */ void addMimeTypeExclusionFilter( const QString &mimeType ); /** * Returns the list of mime type inclusion filters. */ QStringList mimeTypeInclusionFilters() const; /** * Returns the list of mime type exclusion filters. */ QStringList mimeTypeExclusionFilters() const; /** * Clear all mime type filters. */ void clearFilters(); /** * Sets the @p index that shall be used as the root for this model. */ void setRootIndex( const QModelIndex &index ); /** * Sets the header @p set of the filter model. * * \sa EntityTreeModel::HeaderGroup */ void setHeaderSet( int set ); virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; // QAbstractProxyModel does not proxy all methods... virtual bool dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ); virtual QMimeData* mimeData( const QModelIndexList & indexes ) const; virtual QStringList mimeTypes() const; virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const; + virtual bool canFetchMore(const QModelIndex &parent) const; + /** Reimplemented to handle the AmazingCompletionRole. */ virtual QModelIndexList match( const QModelIndex& start, int role, const QVariant& value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags( Qt::MatchStartsWith | Qt::MatchWrap ) ) const; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; protected: virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const; private: //@cond PRIVATE Q_DECLARE_PRIVATE( EntityFilterProxyModel ) - EntityFilterProxyModelPrivate *d_ptr; + EntityFilterProxyModelPrivate * const d_ptr; //@endcond }; } #endif diff --git a/akonadi/entityhiddenattribute.cpp b/akonadi/entityhiddenattribute.cpp new file mode 100644 index 000000000..ea7da259d --- /dev/null +++ b/akonadi/entityhiddenattribute.cpp @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * File : entityhiddenattribute.cpp + * Creation date : Sat 1 Aug 2009 02:07:50 + * + * Copyright (c) 2009 Szymon Stefanek + * + * 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 "entityhiddenattribute.h" + +#include + +namespace Akonadi +{ + +EntityHiddenAttribute::EntityHiddenAttribute() +{ +} + +EntityHiddenAttribute::~EntityHiddenAttribute() +{ +} + +QByteArray Akonadi::EntityHiddenAttribute::type() const +{ + return "HIDDEN"; +} + +EntityHiddenAttribute * EntityHiddenAttribute::clone() const +{ + return new EntityHiddenAttribute(); +} + +QByteArray EntityHiddenAttribute::serialized() const +{ + return QByteArray(); +} + +void EntityHiddenAttribute::deserialize( const QByteArray &data ) +{ + Q_ASSERT( data.isEmpty() ); +} + +} // namespace Akonadi + diff --git a/akonadi/entityhiddenattribute.h b/akonadi/entityhiddenattribute.h new file mode 100644 index 000000000..eb941c8ed --- /dev/null +++ b/akonadi/entityhiddenattribute.h @@ -0,0 +1,85 @@ +/****************************************************************************** + * + * File : entityhiddenattribute.h + * Creation date : Sat 1 Aug 2009 02:07:50 + * + * Copyright (c) 2009 Szymon Stefanek + * + * 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 _ENTITYHIDDENATTRIBUTE_H_ +#define _ENTITYHIDDENATTRIBUTE_H_ + +#include + + +namespace Akonadi { + +/** + * @short Attribute signaling that an entity should be hidden in the UI + * + * This class rappresents the attribute of all hidden items. The hidden + * items shouldn't be displayed in UI applications (unless in some kind + * of "debug" mode). + * + * @seealso Attribute + * + * @author Szymon Stefanek + * @since 4.4 + */ +class AKONADI_EXPORT EntityHiddenAttribute : public Attribute +{ + public: + + /** + * Creates a new entity hidden attribute. + */ + EntityHiddenAttribute(); + + /** + * Destroys the entity hidden attribute. + */ + ~EntityHiddenAttribute(); + + public: + + /** + * Reimplemented from Attribute + */ + QByteArray type() const; + + /** + * Reimplemented from Attribute + */ + EntityHiddenAttribute * clone() const; + + /** + * Reimplemented from Attribute + */ + QByteArray serialized() const; + + /** + * Reimplemented from Attribute + */ + void deserialize( const QByteArray &data ); + +}; // EntityHiddenAttribute + +} // namespace Akonadi + +#endif //!_ENTITYHIDDENATTRIBUTE_H_ diff --git a/akonadi/entitytreemodel.cpp b/akonadi/entitytreemodel.cpp index ad8ebe92f..3155e0e65 100644 --- a/akonadi/entitytreemodel.cpp +++ b/akonadi/entitytreemodel.cpp @@ -1,943 +1,946 @@ /* Copyright (c) 2008 Stephen Kelly 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 "entitytreemodel.h" #include "entitytreemodel_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionutils_p.h" #include "kdebug.h" using namespace Akonadi; EntityTreeModel::EntityTreeModel( Session *session, Monitor *monitor, QObject *parent ) : QAbstractItemModel( parent ), d_ptr( new EntityTreeModelPrivate( this ) ) { Q_D( EntityTreeModel ); d->m_monitor = monitor; d->m_session = session; d->m_includeStatistics = true; d->m_monitor->fetchCollectionStatistics( true ); d->m_mimeChecker.setWantedMimeTypes( d->m_monitor->mimeTypesMonitored() ); connect( monitor, SIGNAL( mimeTypeMonitored( const QString&, bool ) ), SLOT( monitoredMimeTypeChanged( const QString&, bool ) ) ); // monitor collection changes connect( monitor, SIGNAL( collectionChanged( const Akonadi::Collection& ) ), SLOT( monitoredCollectionChanged( const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( collectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ), SLOT( monitoredCollectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( collectionRemoved( const Akonadi::Collection& ) ), SLOT( monitoredCollectionRemoved( const Akonadi::Collection&) ) ); // connect( monitor, // SIGNAL( collectionMoved( const Akonadi::Collection &, const Akonadi::Collection &, const Akonadi::Collection & ) ), // SLOT( monitoredCollectionMoved( const Akonadi::Collection &, const Akonadi::Collection &, const Akonadi::Collection & ) ) ); //TODO: Figure out if the monitor emits these signals even without an item fetch scope. // Wrap them in an if() if so. // Don't want to be adding items to a model if NoItemPopulation is set. // If LazyPopulation is set, then we'll have to add items to collections which // have already been lazily populated. // Monitor item changes. connect( monitor, SIGNAL( itemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ), SLOT( monitoredItemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) ); connect( monitor, SIGNAL( itemChanged( const Akonadi::Item&, const QSet& ) ), SLOT( monitoredItemChanged( const Akonadi::Item&, const QSet& ) ) ); connect( monitor, SIGNAL( itemRemoved( const Akonadi::Item& ) ), SLOT( monitoredItemRemoved( const Akonadi::Item& ) ) ); //connect( monitor, SIGNAL( itemMoved( const Akonadi::Item, const Akonadi::Collection, const Akonadi::Collection ) ), // SLOT( monitoredItemMoved( const Akonadi::Item, const Akonadi::Collection, const Akonadi::Collection ) ) ); connect( monitor, SIGNAL( collectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ), SLOT(monitoredCollectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ) ); connect( monitor, SIGNAL( itemLinked( const Akonadi::Item&, const Akonadi::Collection& )), SLOT( monitoredItemLinked( const Akonadi::Item&, const Akonadi::Collection& ))); connect( monitor, SIGNAL( itemUnlinked( const Akonadi::Item&, const Akonadi::Collection& )), SLOT( monitoredItemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ))); // connect( q, SIGNAL( modelReset() ), q, SLOT( slotModelReset() ) ); d->m_rootCollection = Collection::root(); d->m_rootCollectionDisplayName = QLatin1String( "[*]" ); // Initializes the model cleanly. clearAndReset(); } EntityTreeModel::~EntityTreeModel() { Q_D( EntityTreeModel ); foreach( QList list, d->m_childEntities ) { qDeleteAll(list); list.clear(); } delete d_ptr; } void EntityTreeModel::clearAndReset() { Q_D( EntityTreeModel ); d->m_collections.clear(); d->m_items.clear(); d->m_childEntities.clear(); reset(); QTimer::singleShot( 0, this, SLOT( startFirstListJob() ) ); } Collection EntityTreeModel::collectionForId( Collection::Id id ) const { Q_D( const EntityTreeModel ); return d->m_collections.value( id ); } Item EntityTreeModel::itemForId( Item::Id id ) const { Q_D( const EntityTreeModel ); return d->m_items.value( id ); } int EntityTreeModel::columnCount( const QModelIndex & parent ) const { // TODO: Statistics? if ( parent.isValid() && parent.column() != 0 ) return 0; return 1; } QVariant EntityTreeModel::getData( const Item &item, int column, int role ) const { if ( column == 0 ) { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: if ( item.hasAttribute() && !item.attribute()->displayName().isEmpty() ) { return item.attribute()->displayName(); } else { return item.remoteId(); } break; case Qt::DecorationRole: if ( item.hasAttribute() && !item.attribute()->iconName().isEmpty() ) return item.attribute()->icon(); break; case MimeTypeRole: return item.mimeType(); break; case RemoteIdRole: return item.remoteId(); break; case ItemRole: return QVariant::fromValue( item ); break; case ItemIdRole: return item.id(); break; default: break; } } return QVariant(); } QVariant EntityTreeModel::getData( const Collection &collection, int column, int role ) const { Q_D(const EntityTreeModel); if ( column > 0 ) return QString(); if ( collection == Collection::root() ) { // Only display the root collection. It may not be edited. if ( role == Qt::DisplayRole ) return d->m_rootCollectionDisplayName; if ( role == Qt::EditRole ) return QVariant(); } if ( column == 0 && (role == Qt::DisplayRole || role == Qt::EditRole) ) { if ( collection.hasAttribute() && !collection.attribute()->displayName().isEmpty() ) return collection.attribute()->displayName(); return collection.name(); } switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: if ( column == 0 ) { if ( collection.hasAttribute() && !collection.attribute()->displayName().isEmpty() ) { return collection.attribute()->displayName(); } return collection.name(); } break; case Qt::DecorationRole: if ( collection.hasAttribute() && !collection.attribute()->iconName().isEmpty() ) { return collection.attribute()->icon(); } return KIcon( CollectionUtils::defaultIconName( collection ) ); break; case MimeTypeRole: return collection.mimeType(); break; case RemoteIdRole: return collection.remoteId(); break; case CollectionIdRole: return collection.id(); break; case CollectionRole: { return QVariant::fromValue( collection ); break; } default: break; } return QVariant(); } QVariant EntityTreeModel::data( const QModelIndex & index, int role ) const { const int headerSet = (role / TerminalUserRole); role %= TerminalUserRole; if ( !index.isValid() ) { if (ColumnCountRole != role) return QVariant(); return getColumnCount(headerSet); } if (ColumnCountRole == role) return getColumnCount(headerSet); - + Q_D( const EntityTreeModel ); const Node *node = reinterpret_cast( index.internalPointer() ); - if (ParentCollection == role) + if (ParentCollectionRole == role) { const Collection parentCollection = d->m_collections.value( node->parent ); Q_ASSERT(parentCollection.isValid()); return QVariant::fromValue(parentCollection); } if ( Node::Collection == node->type ) { const Collection collection = d->m_collections.value( node->id ); if ( !collection.isValid() ) return QVariant(); return getData( collection, index.column(), role ); } else if ( Node::Item == node->type ) { const Item item = d->m_items.value( node->id ); if ( !item.isValid() ) return QVariant(); return getData( item, index.column(), role ); } return QVariant(); } Qt::ItemFlags EntityTreeModel::flags( const QModelIndex & index ) const { Q_D( const EntityTreeModel ); // Pass modeltest. // http://labs.trolltech.com/forums/topic/79 if ( !index.isValid() ) return 0; Qt::ItemFlags flags = QAbstractItemModel::flags( index ); // Only show and enable items in columns other than 0. if ( index.column() != 0 ) return flags; const Node *node = reinterpret_cast(index.internalPointer()); if ( Node::Collection == node->type ) { const Collection collection = d->m_collections.value( node->id ); if ( collection.isValid() ) { if ( collection == Collection::root() ) { // Selectable and displayable only. return flags; } const int rights = collection.rights(); if ( rights & Collection::CanChangeCollection ) { flags |= Qt::ItemIsEditable; // Changing the collection includes changing the metadata (child entityordering). // Need to allow this by drag and drop. flags |= Qt::ItemIsDropEnabled; } if ( rights & Collection::CanDeleteCollection ) { // If this collection is moved, it will need to be deleted flags |= Qt::ItemIsDragEnabled; } if ( rights & ( Collection::CanCreateCollection | Collection::CanCreateItem ) ) { // Can we drop new collections and items into this collection? flags |= Qt::ItemIsDropEnabled; } } } else if ( Node::Item == node->type ) { // Rights come from the parent collection. const Node *parentNode = reinterpret_cast( index.parent().internalPointer() ); // TODO: Is this right for the root collection? I think so, but only by chance. // But will it work if m_rootCollection is different from Collection::root? // Should probably rely on index.parent().isValid() for that. const Collection parentCollection = d->m_collections.value( parentNode->id ); if ( parentCollection.isValid() ) { const int rights = parentCollection.rights(); // Can't drop onto items. if ( rights & Collection::CanChangeItem ) { flags = flags | Qt::ItemIsEditable; } if ( rights & Collection::CanDeleteItem ) { // If this item is moved, it will need to be deleted from its parent. flags = flags | Qt::ItemIsDragEnabled; } } } return flags; } Qt::DropActions EntityTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList EntityTreeModel::mimeTypes() const { // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example. return QStringList() << QLatin1String( "text/uri-list" ); } bool EntityTreeModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) { Q_D( EntityTreeModel ); // TODO Use action and collection rights and return false if necessary // if row and column are -1, then the drop was on parent directly. // data should then be appended on the end of the items of the collections as appropriate. // That will mean begin insert rows etc. // Otherwise it was a sibling of the row^th item of parent. // That will need to be handled by a proxy model. This one can't handle ordering. // if parent is invalid the drop occurred somewhere on the view that is no model, and corresponds to the root. kDebug() << "ismove" << ( action == Qt::MoveAction ); if ( action == Qt::IgnoreAction ) return true; // Shouldn't do this. Need to be able to drop vcards for example. // if (!data->hasFormat("text/uri-list")) // return false; // TODO This is probably wrong and unnecessary. if ( column > 0 ) return false; const Node *node = reinterpret_cast( parent.internalId() ); if ( Node::Item == node->type ) { // Can't drop data onto an item, although we can drop data between items. return false; // TODO: Maybe if it's a drop on an item I should drop below the item instead? // Find out what others do. } if ( Node::Collection == node->type ) { const Collection destCollection = d->m_collections.value( node->id ); // Applications can't create new collections in root. Only resources can. if ( destCollection == Collection::root() ) return false; if ( data->hasFormat( QLatin1String( "text/uri-list" ) ) ) { MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes( destCollection.contentMimeTypes() ); TransactionSequence *transaction = new TransactionSequence( d->m_session ); const KUrl::List urls = KUrl::List::fromMimeData( data ); foreach ( const KUrl &url, urls ) { const Collection collection = d->m_collections.value( Collection::fromUrl( url ).id() ); if ( collection.isValid() ) { if ( !mimeChecker.isWantedCollection( collection ) ) return false; if ( Qt::MoveAction == action ) { // new CollectionMoveJob(col, destCol, transaction); } else if ( Qt::CopyAction == action ) { CollectionCopyJob *collectionCopyJob = new CollectionCopyJob( collection, destCollection, transaction ); connect( collectionCopyJob, SIGNAL( result( KJob* ) ), SLOT( copyJobDone( KJob* ) ) ); } } else { const Item item = d->m_items.value( Item::fromUrl( url ).id() ); if ( item.isValid() ) { if ( Qt::MoveAction == action ) { ItemMoveJob *itemMoveJob = new ItemMoveJob( item, destCollection, transaction ); connect( itemMoveJob, SIGNAL( result( KJob* ) ), SLOT( moveJobDone( KJob* ) ) ); } else if ( Qt::CopyAction == action ) { ItemCopyJob *itemCopyJob = new ItemCopyJob( item, destCollection, transaction); connect( itemCopyJob, SIGNAL( result( KJob* ) ), SLOT( copyJobDone( KJob* ) ) ); } } else { // A uri, but not an akonadi url. What to do? // Should handle known mimetypes like vcards first. // That should make any remaining uris meaningless at this point. } } } return false; // ### Return false so that the view does not update with the dropped // in place where they were dropped. That will be done when the monitor notifies the model // through collectionsReceived that the move was successful. } else { // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do // fromMimeData for them. Hmm, put it in the same transaction with the above? // TODO: This should be handled first, not last. } } return false; } QModelIndex EntityTreeModel::index( int row, int column, const QModelIndex & parent ) const { Q_D( const EntityTreeModel ); if ( parent.column() > 0 ) { - return QModelIndex(); + return QModelIndex(); } - + //TODO: don't use column count here? Use some d-> func. if ( column >= columnCount() || column < 0 ) return QModelIndex(); QList childEntities; const Node *parentNode = reinterpret_cast( parent.internalPointer() ); if ( !parentNode || !parent.isValid() ) { if ( d->m_showRootCollection ) childEntities << d->m_childEntities.value( -1 ); else childEntities = d->m_childEntities.value( d->m_rootCollection.id() ); } else { if ( parentNode->id >= 0 ) childEntities = d->m_childEntities.value( parentNode->id ); } const int size = childEntities.size(); if ( row < 0 || row >= size ) return QModelIndex(); Node *node = childEntities.at( row ); return createIndex( row, column, reinterpret_cast( node ) ); } QModelIndex EntityTreeModel::parent( const QModelIndex & index ) const { Q_D( const EntityTreeModel ); if ( !index.isValid() ) return QModelIndex(); const Node *node = reinterpret_cast( index.internalPointer() ); if ( !node ) return QModelIndex(); const Collection collection = d->m_collections.value( node->parent ); if ( !collection.isValid() ) return QModelIndex(); if ( collection.id() == d->m_rootCollection.id() ) { if ( !d->m_showRootCollection ) return QModelIndex(); else return createIndex( 0, 0, reinterpret_cast( d->m_rootNode ) ); } const int row = d->indexOf( d->m_childEntities.value( collection.parentCollection().id()), collection.id() ); Node *parentNode = d->m_childEntities.value( collection.parentCollection().id() ).at( row ); return createIndex( row, 0, reinterpret_cast( parentNode ) ); } int EntityTreeModel::rowCount( const QModelIndex & parent ) const { Q_D( const EntityTreeModel ); const Node *node = reinterpret_cast( parent.internalPointer() ); qint64 id; if ( !parent.isValid() ) { // If we're showing the root collection then it will be the only child of the root. if ( d->m_showRootCollection ) return d->m_childEntities.value( -1 ).size(); id = d->m_rootCollection.id(); } else { if ( !node ) return 0; if ( Node::Item == node->type ) return 0; id = node->id; } if ( parent.column() <= 0 ) return d->m_childEntities.value( id ).size(); return 0; } int EntityTreeModel::getColumnCount(int headerSet) const { // Not needed in this model. Q_UNUSED(headerSet); return columnCount(); } QVariant EntityTreeModel::getHeaderData( int section, Qt::Orientation orientation, int role, int headerSet) const { // Not needed in this model. Q_UNUSED(headerSet); if ( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole ) return i18nc( "@title:column, name of a thing", "Name" ); return QAbstractItemModel::headerData( section, orientation, role ); } QVariant EntityTreeModel::headerData( int section, Qt::Orientation orientation, int role ) const { const int headerSet = (role / TerminalUserRole); role %= TerminalUserRole; return getHeaderData( section, orientation, role, headerSet ); } QMimeData *EntityTreeModel::mimeData( const QModelIndexList &indexes ) const { Q_D( const EntityTreeModel ); QMimeData *data = new QMimeData(); KUrl::List urls; foreach( const QModelIndex &index, indexes ) { if ( index.column() != 0 ) continue; if (!index.isValid()) continue; const Node *node = reinterpret_cast( index.internalPointer() ); if ( Node::Collection == node->type ) urls << d->m_collections.value(node->id).url(); else if ( Node::Item == node->type ) urls << d->m_items.value( node->id ).url( Item::UrlWithMimeType ); else // if that happens something went horrible wrong Q_ASSERT(false); } urls.populateMimeData( data ); return data; } // Always return false for actions which take place asyncronously, eg via a Job. bool EntityTreeModel::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_D( EntityTreeModel ); const Node *node = reinterpret_cast( index.internalPointer() ); if ( index.column() == 0 && (role & (Qt::EditRole | ItemRole | CollectionRole)) ) { if ( Node::Collection == node->type ) { Collection collection = d->m_collections.value( node->id ); if ( !collection.isValid() || !value.isValid() ) return false; if ( Qt::EditRole == role ) { collection.setName( value.toString() ); if ( collection.hasAttribute() ) { EntityDisplayAttribute *displayAttribute = collection.attribute(); displayAttribute->setDisplayName( value.toString() ); collection.addAttribute( displayAttribute ); } } if ( CollectionRole == role ) collection = value.value(); CollectionModifyJob *job = new CollectionModifyJob( collection, d->m_session ); connect( job, SIGNAL( result( KJob* ) ), SLOT( updateJobDone( KJob* ) ) ); return false; } else if (Node::Item == node->type) { Item item = d->m_items.value( node->id ); if ( !item.isValid() || !value.isValid() ) return false; if ( Qt::EditRole == role ) { if ( item.hasAttribute() ) { EntityDisplayAttribute *displayAttribute = item.attribute( Entity::AddIfMissing ); displayAttribute->setDisplayName( value.toString() ); item.addAttribute( displayAttribute ); } } if ( ItemRole == role ) item = value.value(); ItemModifyJob *itemModifyJob = new ItemModifyJob( item, d->m_session ); connect( itemModifyJob, SIGNAL( result( KJob* ) ), SLOT( updateJobDone( KJob* ) ) ); return false; } } return QAbstractItemModel::setData( index, value, role ); } bool EntityTreeModel::canFetchMore( const QModelIndex & parent ) const { Q_D(const EntityTreeModel); const Item item = parent.data( ItemRole ).value(); if ( item.isValid() ) { // items can't have more rows. // TODO: Should I use this for fetching more of an item, ie more payload parts? return false; } else { // but collections can... const Collection::Id colId = parent.data( CollectionIdRole ).toULongLong(); // But the root collection can't... if ( Collection::root().id() == colId ) { return false; } - + foreach (Node *node, d->m_childEntities.value( colId ) ) { if ( Node::Item == node->type ) { // Only try to fetch more from a collection if we don't already have items in it. // Otherwise we'd spend all the time listing items in collections. // This means that collections which don't contain items get a lot of item fetch jobs started on them. // Will fix that later. return false; } } return true; } // TODO: It might be possible to get akonadi to tell us if a collection is empty // or not and use that information instead of assuming all collections are not empty. // Using Collection statistics? } void EntityTreeModel::fetchMore( const QModelIndex & parent ) { Q_D( EntityTreeModel ); + if (!canFetchMore(parent)) + return; + if ( d->m_itemPopulation == ImmediatePopulation ) // Nothing to do. The items are already in the model. return; else if ( d->m_itemPopulation == LazyPopulation ) { const Collection collection = parent.data( CollectionRole ).value(); if ( !collection.isValid() ) return; d->fetchItems( collection ); } } bool EntityTreeModel::hasChildren( const QModelIndex &parent ) const { Q_D( const EntityTreeModel ); // TODO: Empty collections right now will return true and get a little + to expand. // There is probably no way to tell if a collection // has child items in akonadi without first attempting an itemFetchJob... // Figure out a way to fix this. (Statistics) return ((rowCount(parent) > 0) || (canFetchMore( parent ) && d->m_itemPopulation == LazyPopulation)); } bool EntityTreeModel::match(const Item &item, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(item); Q_UNUSED(value); Q_UNUSED(flags); return false; } bool EntityTreeModel::match(const Collection &collection, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(collection); Q_UNUSED(value); Q_UNUSED(flags); return false; } QModelIndexList EntityTreeModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const -{ +{ if (role != AmazingCompletionRole) return QAbstractItemModel::match(start, role, value, hits, flags); // Try to match names, and email addresses. QModelIndexList list; if (role < 0 || !start.isValid() || !value.isValid()) return list; - + const int column = 0; int row = start.row(); QModelIndex parentIdx = start.parent(); int parentRowCount = rowCount(parentIdx); while (row < parentRowCount && (hits == -1 || list.size() < hits)) { QModelIndex idx = index(row, column, parentIdx); Item item = idx.data(ItemRole).value(); if (!item.isValid()) { Collection col = idx.data(CollectionRole).value(); if (!col.isValid()) { continue; } if (match(col, value, flags)) list << idx; } else { if (match(item, value, flags)) { list << idx; } } ++row; } return list; } bool EntityTreeModel::insertRows( int, int, const QModelIndex& ) { return false; } bool EntityTreeModel::insertColumns( int, int, const QModelIndex& ) { return false; } bool EntityTreeModel::removeRows( int start, int end, const QModelIndex &parent ) { /* beginRemoveRows(start, end, parent); // TODO: Implement me. endRemoveRows(start, end, parent); */ return false; } bool EntityTreeModel::removeColumns( int, int, const QModelIndex& ) { return false; } void EntityTreeModel::setRootCollection( const Collection &collection ) { Q_D(EntityTreeModel); Q_ASSERT( collection.isValid() ); d->m_rootCollection = collection; clearAndReset(); } Collection EntityTreeModel::rootCollection() const { Q_D(const EntityTreeModel); return d->m_rootCollection; } QModelIndex EntityTreeModel::indexForCollection( const Collection &collection ) const { Q_D(const EntityTreeModel); // TODO: will this work for collection::root while showing it? const int row = d->indexOf( d->m_childEntities.value( collection.parentCollection().id() ), collection.id() ); if ( row < 0 ) return QModelIndex(); Node *node = d->m_childEntities.value( collection.parentCollection().id() ).at( row ); return createIndex( row, 0, reinterpret_cast( node ) ); } QModelIndexList EntityTreeModel::indexesForItem( const Item &item ) const { Q_D(const EntityTreeModel); QModelIndexList indexes; const Collection::List collections = d->getParentCollections( item ); const qint64 id = item.id(); foreach ( const Collection &collection, collections ) { const int row = d->indexOf( d->m_childEntities.value( collection.id() ), id ); Node *node = d->m_childEntities.value( collection.id() ).at( row ); indexes << createIndex( row, 0, reinterpret_cast( node ) ); } return indexes; } void EntityTreeModel::setItemPopulationStrategy( ItemPopulationStrategy strategy ) { Q_D(EntityTreeModel); d->m_itemPopulation = strategy; clearAndReset(); } EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const { Q_D(const EntityTreeModel); return d->m_itemPopulation; } void EntityTreeModel::setIncludeRootCollection( bool include ) { Q_D(EntityTreeModel); d->m_showRootCollection = include; clearAndReset(); } bool EntityTreeModel::includeRootCollection() const { Q_D(const EntityTreeModel); return d->m_showRootCollection; } void EntityTreeModel::setRootCollectionDisplayName( const QString &displayName ) { Q_D(EntityTreeModel); d->m_rootCollectionDisplayName = displayName; // TODO: Emit datachanged if it is being shown. } QString EntityTreeModel::rootCollectionDisplayName() const { Q_D( const EntityTreeModel); return d->m_rootCollectionDisplayName; } void EntityTreeModel::setCollectionFetchStrategy( CollectionFetchStrategy strategy ) { Q_D( EntityTreeModel); d->m_collectionFetchStrategy = strategy; clearAndReset(); } EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const { Q_D( const EntityTreeModel); return d->m_collectionFetchStrategy; } #include "entitytreemodel.moc" diff --git a/akonadi/entitytreemodel.h b/akonadi/entitytreemodel.h index a9b0cb2d0..ff65616a8 100644 --- a/akonadi/entitytreemodel.h +++ b/akonadi/entitytreemodel.h @@ -1,348 +1,348 @@ /* Copyright (c) 2008 Stephen Kelly 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_ENTITYTREEMODEL_H #define AKONADI_ENTITYTREEMODEL_H #include "akonadi_export.h" #include #include #include #include // TODO (Applies to all these 'new' models, not just EntityTreeModel): // * Figure out how LazyPopulation and signals from monitor containing items should // fit together. Possibly store a list of collections whose items have already // been lazily fetched. // * Fgure out whether DescendantEntitiesProxyModel needs to use fetchMore. // * Profile this and DescendantEntitiesProxyModel. Make sure it's faster than // FlatCollectionProxyModel. See if the cache in that class can be cleared less often. // * Unit tests. Much of the stuff here is not covered by modeltest, and some of // it is akonadi specific, such as setting root collection etc. // * Implement support for includeUnsubscribed. // * Use CollectionStatistics for item count stuff. Find out if I can get stats by mimetype. // * Make sure there are applications using it before committing to it until KDE5. // Some API/ virtual methods might need to be added when real applications are made. // * Implement ordering support. // * Implement some proxy models for time-table like uses, eg KOrganizer events. // * Apidox++ namespace Akonadi { class CollectionStatistics; class Item; class ItemFetchScope; class Monitor; class Session; class EntityTreeModelPrivate; /** * @short A model for collections and items together. * * This class is a wrapper around a Akonadi::Monitor object. The model represents a * part of the collection and item tree configured in the Monitor. * * @code * * Monitor *monitor = new Monitor(this); * monitor->setCollectionMonitored(Collection::root()); * monitor->setMimeTypeMonitored(KABC::addresseeMimeType()); * * EntityTreeModel *model = new EntityTreeModel( session, monitor, this ); * * EntityTreeView *view = new EntityTreeView( this ); * view->setModel( model ); * * @endcode * * @author Stephen Kelly * @since 4.4 */ class AKONADI_EXPORT EntityTreeModel : public QAbstractItemModel { Q_OBJECT public: /** * Describes the roles for items. Roles for collections are defined by the superclass. */ enum Roles { //sebsauer, 2009-05-07; to be able here to keep the akonadi_next EntityTreeModel compatible with //the akonadi_old ItemModel and CollectionModel, we need to use the same int-values for //ItemRole, ItemIdRole and MimeTypeRole like the Akonadi::ItemModel is using and the same //CollectionIdRole and CollectionRole like the Akonadi::CollectionModel is using. ItemIdRole = Qt::UserRole + 1, ///< The item id ItemRole = Qt::UserRole + 2, ///< The Item MimeTypeRole = Qt::UserRole + 3, ///< The mimetype of the entity CollectionIdRole = Qt::UserRole + 10, ///< The collection id. CollectionRole = Qt::UserRole + 11, ///< The collection. RemoteIdRole, ///< The remoteId of the entity CollectionChildOrderRole, ///< Ordered list of child items if available AmazingCompletionRole, ///< Role used to implement amazing completion - ParentCollection, ///< The parent collection of the entity + ParentCollectionRole, ///< The parent collection of the entity ColumnCountRole, ///< @internal Used by proxies to determine the number of columns for a header group. UserRole = Qt::UserRole + 1000, ///< Role for user extensions. TerminalUserRole = 10000 ///< Last role for user extensions. Don't use a role beyond this or headerData will break. }; /** * Describes what header information the model shall return. */ enum HeaderGroup { EntityTreeHeaders, ///< Header information for a tree with collections and items CollectionTreeHeaders, ///< Header information for a collection-only tree ItemListHeaders, ///< Header information for a list of items UserHeaders = 1000 ///< Last header information for submodel extensions }; /** * Creates a new entity tree model. * * @param session The Session to use to communicate with Akonadi. * @param monitor The Monitor whose entities should be represented in the model. * @param parent The parent object. */ EntityTreeModel( Session *session, Monitor *monitor, QObject *parent = 0 ); /** * Destroys the entity tree model. */ virtual ~EntityTreeModel(); /** * Describes how the model should populated its items. */ enum ItemPopulationStrategy { NoItemPopulation, ///< Do not include items in the model. ImmediatePopulation, ///< Retrieve items immediately when their parent is in the model. This is the default. LazyPopulation ///< Fetch items only when requested (using canFetchMore/fetchMore) }; /** * Sets the item population @p strategy of the model. */ void setItemPopulationStrategy( ItemPopulationStrategy strategy ); /** * Returns the item population strategy of the model. */ ItemPopulationStrategy itemPopulationStrategy() const; /** * Sets the root collection to create an entity tree for. * The @p collection must be a valid Collection object. * * By default the Collection::root() is used. */ void setRootCollection( const Collection &collection ); /** * Returns the root collection of the entity tree. */ Collection rootCollection() const; /** * Sets whether the root collection shall be provided by the model. * * @see setRootCollectionDisplayName() */ void setIncludeRootCollection( bool include ); /** * Returns whether the root collection is provided by the model. */ bool includeRootCollection() const; /** * Sets the display @p name of the root collection of the model. * The default display name is "[*]". * * @note The display name for the root collection is only used if * the root collection has been included with setIncludeRootCollection(). */ void setRootCollectionDisplayName( const QString &name ); /** * Returns the display name of the root collection. */ QString rootCollectionDisplayName() const; /** * Describes what collections shall be fetched by and represent in the model. */ enum CollectionFetchStrategy { FetchNoCollections, ///< Fetches nothing. This creates an empty model. FetchFirstLevelChildCollections, ///< Fetches first level collections in the root collection. FetchCollectionsRecursive ///< Fetches collections in the root collection recursively. This is the default. }; /** * Sets the collection fetch @p strategy of the model. */ void setCollectionFetchStrategy( CollectionFetchStrategy strategy ); /** * Returns the collection fetch strategy of the model. */ CollectionFetchStrategy collectionFetchStrategy() const; /** * Returns the model index for the given @p collection. */ QModelIndex indexForCollection( const Collection &collection ) const; /** * Returns the model indexes for the given @p item. */ QModelIndexList indexesForItem( const Item &item ) const; /** * Returns the collection for the given collection @p id. */ Collection collectionForId( Collection::Id id ) const; /** * Returns the item for the given item @p id. */ Item itemForId( Item::Id id ) const; // TODO: Remove these and use the Monitor instead. Need to add api to Monitor for this. void setIncludeUnsubscribed( bool include ); bool includeUnsubscribed() const; virtual int columnCount( const QModelIndex & parent = QModelIndex() ) const; virtual int rowCount( const QModelIndex & parent = QModelIndex() ) const; virtual QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; virtual Qt::ItemFlags flags( const QModelIndex &index ) const; virtual QStringList mimeTypes() const; virtual Qt::DropActions supportedDropActions() const; virtual QMimeData *mimeData( const QModelIndexList &indexes ) const; virtual bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ); virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ); virtual QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex() ) const; virtual QModelIndex parent( const QModelIndex & index ) const; // TODO: Review the implementations of these. I think they could be better. virtual bool canFetchMore( const QModelIndex & parent ) const; virtual void fetchMore( const QModelIndex & parent ); virtual bool hasChildren( const QModelIndex &parent = QModelIndex() ) const; /** * Reimplemented to handle the AmazingCompletionRole. */ virtual QModelIndexList match( const QModelIndex& start, int role, const QVariant& value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags( Qt::MatchStartsWith | Qt::MatchWrap ) ) const; /** * Reimplement this in a subclass to return true if @p item matches @p value with @p flags in the AmazingCompletionRole. */ virtual bool match( const Item &item, const QVariant &value, Qt::MatchFlags flags ) const; /** * Reimplement this in a subclass to return true if @p collection matches @p value with @p flags in the AmazingCompletionRole. */ virtual bool match( const Collection &collection, const QVariant &value, Qt::MatchFlags flags ) const; protected: /** * Clears and resets the model. Always call this instead of the reset method in the superclass. * Using the reset method will not reliably clear or refill the model. */ void clearAndReset(); /** * Provided for convenience of subclasses. */ virtual QVariant getData( const Item &item, int column, int role = Qt::DisplayRole ) const; /** * Provided for convenience of subclasses. */ virtual QVariant getData( const Collection &collection, int column, int role = Qt::DisplayRole ) const; /** * Reimplement this to provide different header data. This is needed when using one model * with multiple proxies and views, and each should show different header data. */ virtual QVariant getHeaderData( int section, Qt::Orientation orientation, int role, int headerSet ) const; virtual int getColumnCount(int headerSet) const; - + /** * Removes the rows from @p start to @p end from @parent */ virtual bool removeRows( int start, int end, const QModelIndex &parent = QModelIndex() ); private: //@cond PRIVATE Q_DECLARE_PRIVATE( EntityTreeModel ) - EntityTreeModelPrivate *d_ptr; + EntityTreeModelPrivate * const d_ptr; // Make these private, they shouldn't be called by applications virtual bool insertRows( int , int, const QModelIndex& = QModelIndex() ); virtual bool insertColumns( int, int, const QModelIndex& = QModelIndex() ); virtual bool removeColumns( int, int, const QModelIndex& = QModelIndex() ); Q_PRIVATE_SLOT( d_func(), void monitoredCollectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ) ) Q_PRIVATE_SLOT( d_func(), void startFirstListJob() ) // Q_PRIVATE_SLOT( d_func(), void slotModelReset() ) // TODO: Can I merge these into one jobResult slot? Q_PRIVATE_SLOT( d_func(), void fetchJobDone( KJob *job ) ) Q_PRIVATE_SLOT( d_func(), void copyJobDone( KJob *job ) ) Q_PRIVATE_SLOT( d_func(), void moveJobDone( KJob *job ) ) Q_PRIVATE_SLOT( d_func(), void updateJobDone( KJob *job ) ) Q_PRIVATE_SLOT( d_func(), void itemsFetched( Akonadi::Item::List ) ) Q_PRIVATE_SLOT( d_func(), void collectionsFetched( Akonadi::Collection::List ) ) Q_PRIVATE_SLOT( d_func(), void ancestorsFetched( Akonadi::Collection::List ) ) Q_PRIVATE_SLOT( d_func(), void monitoredMimeTypeChanged( const QString&, bool ) ) Q_PRIVATE_SLOT( d_func(), void monitoredCollectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredCollectionRemoved( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredCollectionChanged( const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredCollectionMoved( const Akonadi::Collection&, const Akonadi::Collection&, const Akonadi::Collection&) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemAdded( const Akonadi::Item&, const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemRemoved( const Akonadi::Item& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemChanged( const Akonadi::Item&, const QSet& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemLinked( const Akonadi::Item&, const Akonadi::Collection& ) ) Q_PRIVATE_SLOT( d_func(), void monitoredItemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ) ) //@endcond }; } // namespace #endif diff --git a/akonadi/entitytreemodel_p.cpp b/akonadi/entitytreemodel_p.cpp index 9bfe88ec3..ae3f3d7f2 100644 --- a/akonadi/entitytreemodel_p.cpp +++ b/akonadi/entitytreemodel_p.cpp @@ -1,765 +1,789 @@ /* Copyright (c) 2008 Stephen Kelly 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 "entitytreemodel_p.h" #include "entitytreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; EntityTreeModelPrivate::EntityTreeModelPrivate( EntityTreeModel *parent ) : q_ptr( parent ), m_collectionFetchStrategy( EntityTreeModel::FetchCollectionsRecursive ), m_itemPopulation( EntityTreeModel::ImmediatePopulation ), m_includeUnsubscribed( true ), m_includeStatistics( false ), m_showRootCollection( false ) { } int EntityTreeModelPrivate::indexOf( const QList &nodes, Entity::Id id ) const { int i = 0; foreach ( const Node *node, nodes ) { if ( node->id == id ) return i; i++; } return -1; } -void EntityTreeModelPrivate::fetchItems( const Collection &parent ) +ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Collection &parent, ItemFetchScope scope ) const { - Q_Q( EntityTreeModel ); -// kDebug() << parent.remoteId(); - Akonadi::ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session ); - itemJob->setFetchScope( m_monitor->itemFetchScope() ); + ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session ); + itemJob->setFetchScope( scope ); + return itemJob; +} + +ItemFetchJob* EntityTreeModelPrivate::getItemFetchJob( const Item &item, ItemFetchScope scope ) const +{ + ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( item, m_session ); + itemJob->setFetchScope( scope ); + return itemJob; +} + +void EntityTreeModelPrivate::runItemFetchJob(ItemFetchJob *itemFetchJob, const Collection &parent) const +{ + Q_Q( const EntityTreeModel ); + // TODO: This hack is probably not needed anymore. Remove it. // ### HACK: itemsReceivedFromJob needs to know which collection items were added to. // That is not provided by akonadi, so we attach it in a property. - itemJob->setProperty( ItemFetchCollectionId(), QVariant( parent.id() ) ); + itemFetchJob->setProperty( ItemFetchCollectionId(), QVariant( parent.id() ) ); - q->connect( itemJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), + q->connect( itemFetchJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), q, SLOT( itemsFetched( const Akonadi::Item::List& ) ) ); - q->connect( itemJob, SIGNAL( result( KJob* ) ), + q->connect( itemFetchJob, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); + +} + +void EntityTreeModelPrivate::fetchItems( const Collection &parent ) +{ + Q_Q( EntityTreeModel ); + + // TODO: Use a more specific fetch scope to get only the envelope for mails etc. + ItemFetchJob *itemJob = getItemFetchJob(parent, m_monitor->itemFetchScope() ); + + runItemFetchJob(itemJob, parent); } void EntityTreeModelPrivate::fetchCollections( const Collection &collection, CollectionFetchJob::Type type ) { Q_Q( EntityTreeModel ); CollectionFetchJob *job = new CollectionFetchJob( collection, type, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); job->fetchScope().setContentMimeTypes( m_monitor->mimeTypesMonitored() ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( collectionsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::collectionsFetched( const Akonadi::Collection::List& collections ) { // TODO: refactor this stuff into separate methods for listing resources in Collection::root, and listing collections within resources. Q_Q( EntityTreeModel ); Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self(); Collection::List _collections = collections; forever { int collectionsSize = _collections.size(); QMutableListIterator it(_collections); while (it.hasNext()) { const Collection col = it.next(); const Collection::Id parentId = col.parentCollection().id(); const Collection::Id colId = col.id(); if ( m_collections.contains( parentId ) ) { insertCollection( col, m_collections.value( parentId ) ); if ( m_pendingChildCollections.contains( colId ) ) { QList pendingParentIds = m_pendingChildCollections.value( colId ); foreach(const Collection::Id &id, pendingParentIds) { Collection pendingCollection = m_pendingCollections.value(id); Q_ASSERT( pendingCollection.isValid() ); insertPendingCollection( pendingCollection, col, it ); m_pendingCollections.remove(id); } - if ( !it.findNext(col) && it.findPrevious(col)) + if ( !it.findNext(col) && !it.findPrevious(col) ) { Q_ASSERT("Something went very wrong" == "false"); } m_pendingChildCollections.remove( colId ); } it.remove(); } else { m_pendingCollections.insert( colId, col ); - m_pendingChildCollections[ parentId ].append( colId ); + if ( !m_pendingChildCollections.value( parentId ).contains( colId ) ) + m_pendingChildCollections[ parentId ].append( colId ); } } if ( _collections.isEmpty() ) break; // forever if( _collections.size() == collectionsSize ) { // Didn't process any collections this iteration. // Persist them until the next time collectionsFetched receives collections. kWarning() << "Some collections could not be inserted into the model yet."; break; // forever } } } void EntityTreeModelPrivate::itemsFetched( const Akonadi::Item::List& items ) { Q_Q( EntityTreeModel ); QObject *job = q->sender(); Q_ASSERT( job ); const Collection::Id collectionId = job->property( ItemFetchCollectionId() ).value(); Item::List itemsToInsert; Item::List itemsToUpdate; const Collection collection = m_collections.value( collectionId ); Q_ASSERT( collection.isValid() ); const QList collectionEntities = m_childEntities.value( collectionId ); foreach ( const Item &item, items ) { if ( indexOf( collectionEntities, item.id() ) != -1 ) { itemsToUpdate << item; } else { if ( m_mimeChecker.wantedMimeTypes().isEmpty() || m_mimeChecker.isWantedItem( item ) ) { itemsToInsert << item; } } } if ( itemsToInsert.size() > 0 ) { const int startRow = m_childEntities.value( collectionId ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collectionId ) ); q->beginInsertRows( parentIndex, startRow, startRow + items.size() - 1 ); foreach ( const Item &item, items ) { Item::Id itemId = item.id(); m_items.insert( itemId, item ); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; m_childEntities[ collectionId ].append( node ); } q->endInsertRows(); } } void EntityTreeModelPrivate::monitoredMimeTypeChanged( const QString & mimeType, bool monitored ) { if ( monitored ) m_mimeChecker.addWantedMimeType( mimeType ); else m_mimeChecker.removeWantedMimeType( mimeType ); } void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection& collection) { Q_Q( EntityTreeModel ); // Unlike fetchCollections, this method fetches collections by traversing up, not down. CollectionFetchJob *job = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::Base, m_session ); job->fetchScope().setIncludeUnsubscribed( m_includeUnsubscribed ); job->fetchScope().setIncludeStatistics( m_includeStatistics ); q->connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ), q, SLOT( ancestorsFetched( const Akonadi::Collection::List& ) ) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List& collectionList) { // List is a size of one. foreach(const Collection &collection, collectionList) { // We should find a collection already in the tree before we reach the collection root. // We're looking to bridge a gap here. Q_ASSERT(collection != Collection::root()); // We already checked this either on the previous recursion or in monitoredCollectionAdded. Q_ASSERT(!m_collections.contains(collection.id())); m_ancestors.prepend(collection); if (m_collections.contains(collection.parentCollection().id())) { m_ancestors.prepend( m_collections.value(collection.parentCollection().id()) ); insertAncestors(m_ancestors); } else { retrieveAncestors(collection); } } } void EntityTreeModelPrivate::insertAncestors(const Akonadi::Collection::List& collectionList) { Collection::List::const_iterator it; const Collection::List::const_iterator begin = collectionList.constBegin() + 1; const Collection::List::const_iterator end = collectionList.constEnd(); for (it = begin; it != end; ++it) { insertCollection(*it, *(it-1)); } m_ancestors.clear(); } void EntityTreeModelPrivate::insertCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { Q_ASSERT(collection.isValid()); Q_ASSERT(parent.isValid()); Q_Q( EntityTreeModel ); // TODO: Use order attribute of parent if available // Otherwise prepend collections and append items. Currently this prepends all collections. // Or I can prepend and append for single signals, then 'change' the parent. // QList childCols = m_childEntities.value( parent.id() ); // int row = childCols.size(); // int numChildCols = childCollections.value(parent.id()).size(); const int row = 0; const QModelIndex parentIndex = q->indexForCollection( parent ); q->beginInsertRows( parentIndex, row, row ); m_collections.insert( collection.id(), collection ); Node *node = new Node; node->id = collection.id(); node->parent = parent.id(); node->type = Node::Collection; m_childEntities[ parent.id() ].prepend( node ); q->endInsertRows(); if ( m_itemPopulation == EntityTreeModel::ImmediatePopulation ) fetchItems( collection ); } void EntityTreeModelPrivate::insertPendingCollection( const Akonadi::Collection& collection, const Akonadi::Collection& parent, QMutableListIterator &colIt ) { insertCollection(collection, parent); m_pendingCollections.remove( collection.id() ); if (colIt.findPrevious(collection) || colIt.findNext(collection)) { colIt.remove(); } Q_ASSERT(m_collections.contains(parent.id())); QList pendingChildCollectionsToInsert = m_pendingChildCollections.value(collection.id()); QList::const_iterator it; const QList::const_iterator begin = pendingChildCollectionsToInsert.constBegin(); const QList::const_iterator end = pendingChildCollectionsToInsert.constEnd(); for ( it = begin; it != end; ++it ) { insertPendingCollection(m_pendingCollections.value( *it ), collection, colIt); } m_pendingChildCollections.remove( parent.id() ); } void EntityTreeModelPrivate::monitoredCollectionAdded( const Akonadi::Collection& collection, const Akonadi::Collection& parent ) { // If the resource is removed while populating the model with it, we might still // get some monitor signals. These stale/out-of-order signals can't be completely eliminated // in the akonadi server due to implementation details, so we also handle such signals in the model silently // in all the monior slots. // Stephen Kelly, 28, July 2009 // This is currently temporarily blocked by a uninitialized value bug in the server. // if ( !m_collections.contains( parent.id() ) ) // { // kWarning() << "Got a stale notification for a collection whose parent was already removed." << collection.id() << collection.remoteId(); // return; // } // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we // only get the ones we're interested in from the job, we have to filter on collections received through signals too. if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedCollection( collection ) ) return; if (!m_collections.contains(parent.id())) { // The collection we're interested in is contained in a collection we're not interested in. // We download the ancestors of the collection we're interested in to complete the tree. m_ancestors.prepend(collection); retrieveAncestors(collection); return; } insertCollection(collection, parent); } void EntityTreeModelPrivate::monitoredCollectionRemoved( const Akonadi::Collection& collection ) { if ( !m_collections.contains( collection.parent() ) ) return; Q_Q( EntityTreeModel ); // This may be a signal for a collection we've already removed by removing its ancestor. if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } const int row = indexOf( m_childEntities.value( collection.parentCollection().id() ), collection.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.parentCollection().id() ) ); q->beginRemoveRows( parentIndex, row, row ); // Delete all descendant collections and items. removeChildEntities(collection.id()); // Remove deleted collection from its parent. m_childEntities[ collection.parentCollection().id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::removeChildEntities(Collection::Id colId) { QList::const_iterator it; QList childList = m_childEntities.value(colId); const QList::const_iterator begin = childList.constBegin(); const QList::const_iterator end = childList.constEnd(); for (it = begin; it != end; ++it) { if (Node::Item == (*it)->type) { m_items.remove((*it)->id); } else { removeChildEntities((*it)->id); m_collections.remove((*it)->id); } } m_childEntities.remove(colId); } void EntityTreeModelPrivate::monitoredCollectionMoved( const Akonadi::Collection& collection, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } Q_Q( EntityTreeModel ); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), collection.id() ); const QModelIndex srcParentIndex = q->indexForCollection( sourceCollection ); const QModelIndex destParentIndex = q->indexForCollection( destCollection ); const int destRow = 0; // Prepend collections // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcParentIndex, srcRow, srcRow, destParentIndex, destRow ); // Node *node = m_childEntities[ sourceCollection.id() ].takeAt( srcRow ); // m_childEntities[ destCollection.id() ].prepend( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredCollectionChanged( const Akonadi::Collection &collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for a collection which was already removed." << collection.id() << collection.remoteId(); return; } m_collections[ collection.id() ] = collection; const QModelIndex index = q->indexForCollection( collection ); Q_ASSERT( index.isValid() ); q->dataChanged( index, index ); } void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged( Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( id ) ) { kWarning() << "Got statistics response for non-existing collection:" << id; } else { m_collections[ id ].setStatistics( statistics ); const QModelIndex index = q->indexForCollection( m_collections[ id ] ); q->dataChanged( index, index ); } } void EntityTreeModelPrivate::monitoredItemAdded( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_collections.contains( collection.id() ) ) { kWarning() << "Got a stale notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_items.insert( item.id(), item ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id() ].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemRemoved( const Akonadi::Item &item ) { Q_Q( EntityTreeModel ); const Collection::List parents = getParentCollections( item ); if ( parents.isEmpty() ) return; if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } // TODO: Iterate over all (virtual) collections. const Collection collection = parents.first(); Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginRemoveRows( parentIndex, row, row ); m_items.remove( item.id() ); m_childEntities[ collection.id() ].removeAt( row ); q->endRemoveRows(); } void EntityTreeModelPrivate::monitoredItemChanged( const Akonadi::Item &item, const QSet& ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } m_items[ item.id() ] = item; const QModelIndexList indexes = q->indexesForItem( item ); foreach ( const QModelIndex &index, indexes ) { if( !index.isValid() ) { kWarning() << "item has invalid index:" << item.id() << item.remoteId(); } else { q->dataChanged( index, index ); } } } void EntityTreeModelPrivate::monitoredItemMoved( const Akonadi::Item& item, const Akonadi::Collection& sourceCollection, const Akonadi::Collection& destCollection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( sourceCollection.id() ) ); Q_ASSERT( m_collections.contains( destCollection.id() ) ); const Item::Id itemId = item.id(); const int srcRow = indexOf( m_childEntities.value( sourceCollection.id() ), itemId ); const QModelIndex srcIndex = q->indexForCollection( sourceCollection ); const QModelIndex destIndex = q->indexForCollection( destCollection ); // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes? const int destRow = q->rowCount( destIndex ); // TODO: Uncomment for Qt4.6 // q->beginMoveRows( srcIndex, srcRow, srcRow, destIndex, destRow ); // Node *node = m_childEntities[ sourceItem.id() ].takeAt( srcRow ); // m_childEntities[ destItem.id() ].append( node ); // q->endMoveRows(); } void EntityTreeModelPrivate::monitoredItemLinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); if ( !m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem( item ) ) return; const int row = m_childEntities.value( collection.id() ).size(); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; m_childEntities[ collection.id()].append( node ); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemUnlinked( const Akonadi::Item& item, const Akonadi::Collection& collection ) { Q_Q( EntityTreeModel ); if ( !m_items.contains( item.id() ) ) { kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT( m_collections.contains( collection.id() ) ); const int row = indexOf( m_childEntities.value( collection.id() ), item.id() ); const QModelIndex parentIndex = q->indexForCollection( m_collections.value( collection.id() ) ); q->beginInsertRows( parentIndex, row, row ); m_childEntities[ collection.id() ].removeAt( row ); q->endInsertRows(); } void EntityTreeModelPrivate::fetchJobDone( KJob *job ) { Q_ASSERT(m_pendingCollections.isEmpty()); Q_ASSERT(m_pendingChildCollections.isEmpty()); if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::copyJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::moveJobDone( KJob *job ) { if ( job->error() ) { kWarning() << "Job error: " << job->errorString() << endl; } } void EntityTreeModelPrivate::updateJobDone( KJob *job ) { if ( job->error() ) { // TODO: handle job errors kWarning() << "Job error:" << job->errorString(); } else { // TODO: Is this trying to do the job of collectionstatisticschanged? // CollectionStatisticsJob *csjob = static_cast( job ); // Collection result = csjob->collection(); // collectionStatisticsChanged( result.id(), csjob->statistics() ); } } void EntityTreeModelPrivate::startFirstListJob() { Q_Q(EntityTreeModel); if (m_collections.size() > 0) return; Collection rootCollection; // Even if the root collection is the invalid collection, we still need to start // the first list job with Collection::root. if ( m_showRootCollection ) { rootCollection = Collection::root(); // Notify the outside that we're putting collection::root into the model. // kDebug() << "begin"; q->beginInsertRows( QModelIndex(), 0, 0 ); m_collections.insert( rootCollection.id(), rootCollection ); m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_childEntities[ -1 ].append( m_rootNode ); // kDebug() << "why"; q->endInsertRows(); } else { // Otherwise store it silently because it's not part of the usable model. rootCollection = m_rootCollection; m_rootNode = new Node; m_rootNode->id = rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_collections.insert( rootCollection.id(), rootCollection ); } // kDebug() << "inserting" << rootCollection.id(); // Includes recursive trees. Lower levels are fetched in the onRowsInserted slot if // necessary. // HACK: fix this for recursive listing if we filter on mimetypes that only exit deeper // in the hierarchy if ( ( m_collectionFetchStrategy == EntityTreeModel::FetchFirstLevelChildCollections) /*|| ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive )*/ ) { fetchCollections( rootCollection, CollectionFetchJob::FirstLevel ); } if ( m_collectionFetchStrategy == EntityTreeModel::FetchCollectionsRecursive ) fetchCollections( rootCollection, CollectionFetchJob::Recursive ); // If the root collection is not collection::root, then it could have items, and they will need to be // retrieved now. if ( m_itemPopulation != EntityTreeModel::NoItemPopulation ) { // kDebug() << (rootCollection == Collection::root()); if (rootCollection != Collection::root()) fetchItems( rootCollection ); } } Collection EntityTreeModelPrivate::getParentCollection( Entity::Id id ) const { QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), id ) != -1 ) { return m_collections.value( iter.key() ); } } return Collection(); } Collection::List EntityTreeModelPrivate::getParentCollections( const Item &item ) const { Collection::List list; QHashIterator > iter( m_childEntities ); while ( iter.hasNext() ) { iter.next(); if ( indexOf( iter.value(), item.id() ) != -1 ) { list << m_collections.value( iter.key() ); } } return list; } Collection EntityTreeModelPrivate::getParentCollection( const Collection &collection ) const { return m_collections.value( collection.parentCollection().id() ); } Entity::Id EntityTreeModelPrivate::childAt( Collection::Id id, int position, bool *ok ) const { const QList list = m_childEntities.value( id ); if ( list.size() <= position ) { *ok = false; return 0; } *ok = true; return list.at( position )->id; } int EntityTreeModelPrivate::indexOf( Collection::Id parent, Collection::Id collectionId ) const { return indexOf( m_childEntities.value( parent ), collectionId ); } Item EntityTreeModelPrivate::getItem( Item::Id id) const { if ( id > 0 ) id *= -1; return m_items.value( id ); } diff --git a/akonadi/entitytreemodel_p.h b/akonadi/entitytreemodel_p.h index 8213120bc..fa5b6718d 100644 --- a/akonadi/entitytreemodel_p.h +++ b/akonadi/entitytreemodel_p.h @@ -1,149 +1,157 @@ /* Copyright (c) 2008 Stephen Kelly 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 ENTITYTREEMODELPRIVATE_H #define ENTITYTREEMODELPRIVATE_H #include #include #include #include #include #include "entitytreemodel.h" +namespace Akonadi +{ +class ItemFetchJob; +} + struct Node { Akonadi::Entity::Id id; Akonadi::Entity::Id parent; enum Type { Item, Collection }; int type; }; namespace Akonadi { /** * @internal */ class EntityTreeModelPrivate { public: EntityTreeModelPrivate( EntityTreeModel *parent ); EntityTreeModel *q_ptr; // void collectionStatisticsChanged( Collection::Id, const Akonadi::CollectionStatistics& ); enum RetrieveDepth { Base, Recursive }; void fetchCollections( const Collection &collection, CollectionFetchJob::Type = CollectionFetchJob::FirstLevel ); void fetchItems( const Collection &collection ); void collectionsFetched( const Akonadi::Collection::List& ); // void resourceTopCollectionsFetched( const Akonadi::Collection::List& ); void itemsFetched( const Akonadi::Item::List& ); void monitoredCollectionAdded( const Akonadi::Collection&, const Akonadi::Collection& ); void monitoredCollectionRemoved( const Akonadi::Collection& ); void monitoredCollectionChanged( const Akonadi::Collection& ); void monitoredCollectionStatisticsChanged( Akonadi::Collection::Id, const Akonadi::CollectionStatistics& ); void monitoredCollectionMoved( const Akonadi::Collection&, const Akonadi::Collection&, const Akonadi::Collection& ); void monitoredItemAdded( const Akonadi::Item&, const Akonadi::Collection& ); void monitoredItemRemoved( const Akonadi::Item& ); void monitoredItemChanged( const Akonadi::Item&, const QSet& ); void monitoredItemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ); void monitoredItemLinked( const Akonadi::Item&, const Akonadi::Collection& ); void monitoredItemUnlinked( const Akonadi::Item&, const Akonadi::Collection& ); void monitoredMimeTypeChanged( const QString &mimeType, bool monitored ); Collection getParentCollection( Entity::Id id ) const; Collection::List getParentCollections( const Item &item ) const; Collection getParentCollection( const Collection &collection ) const; Entity::Id childAt( Collection::Id, int position, bool *ok ) const; int indexOf( Collection::Id parent, Collection::Id id ) const; Item getItem( Item::Id id ) const; void removeChildEntities(Collection::Id colId); void retrieveAncestors(const Akonadi::Collection& collection); void ancestorsFetched(const Akonadi::Collection::List& collectionList); void insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection& parent ); void insertPendingCollection(const Akonadi::Collection &collection, const Akonadi::Collection& parent, QMutableListIterator &it ); void insertAncestors(const Akonadi::Collection::List &collectionList ); - + + ItemFetchJob* getItemFetchJob(const Collection &parent, ItemFetchScope scope) const; + ItemFetchJob* getItemFetchJob(const Item &item, ItemFetchScope scope) const; + void runItemFetchJob(ItemFetchJob* itemFetchJob, const Collection &parent) const; QHash m_collections; QHash m_items; QHash > m_childEntities; QSet m_populatedCols; Collection::List m_ancestors; QHash m_pendingCollections; QHash > m_pendingChildCollections; Monitor *m_monitor; Collection m_rootCollection; Node *m_rootNode; QString m_rootCollectionDisplayName; QStringList m_mimeTypeFilter; MimeTypeChecker m_mimeChecker; EntityTreeModel::CollectionFetchStrategy m_collectionFetchStrategy; EntityTreeModel::ItemPopulationStrategy m_itemPopulation; bool m_includeUnsubscribed; bool m_includeStatistics; bool m_showRootCollection; void startFirstListJob(); void fetchJobDone( KJob *job ); void copyJobDone( KJob *job ); void moveJobDone( KJob *job ); void updateJobDone( KJob *job ); /** * Returns the index of the node in @p list with the id @p id. Returns -1 if not found. */ int indexOf( const QList &list, Entity::Id id ) const; /** * The id of the collection which starts an item fetch job. This is part of a hack with QObject::sender * in itemsReceivedFromJob to correctly insert items into the model. */ static QByteArray ItemFetchCollectionId() { return "ItemFetchCollectionId"; } Session *m_session; Q_DECLARE_PUBLIC( EntityTreeModel ) }; } #endif diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp index 99a7b9b3c..cdd334b0e 100644 --- a/akonadi/protocolhelper.cpp +++ b/akonadi/protocolhelper.cpp @@ -1,229 +1,248 @@ /* Copyright (c) 2008 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 "protocolhelper_p.h" #include "attributefactory.h" #include "collectionstatistics.h" #include "exception.h" #include #include #include #include #include #include #include using namespace Akonadi; int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start) { QVarLengthArray params; int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start ); for ( int i = 0; i < params.count() - 1; i += 2 ) { const QByteArray key = params[i]; const QByteArray value = params[i + 1]; if ( key == "INHERIT" ) policy.setInheritFromParent( value == "true" ); else if ( key == "INTERVAL" ) policy.setIntervalCheckTime( value.toInt() ); else if ( key == "CACHETIMEOUT" ) policy.setCacheTimeout( value.toInt() ); else if ( key == "SYNCONDEMAND" ) policy.setSyncOnDemand( value == "true" ); else if ( key == "LOCALPARTS" ) { QVarLengthArray tmp; QStringList parts; Akonadi::ImapParser::parseParenthesizedList( value, tmp ); for ( int j=0; j attributes; pos = ImapParser::parseParenthesizedList( data, attributes, pos ); for ( int i = 0; i < attributes.count() - 1; i += 2 ) { const QByteArray key = attributes[i]; const QByteArray value = attributes[i + 1]; if ( key == "NAME" ) { collection.setName( QString::fromUtf8( value ) ); } else if ( key == "REMOTEID" ) { collection.setRemoteId( QString::fromUtf8( value ) ); } else if ( key == "RESOURCE" ) { collection.setResource( QString::fromUtf8( value ) ); } else if ( key == "MIMETYPE" ) { QVarLengthArray ct; ImapParser::parseParenthesizedList( value, ct ); QStringList ct2; for ( int j = 0; j < ct.size(); j++ ) ct2 << QString::fromLatin1( ct[j] ); collection.setContentMimeTypes( ct2 ); } else if ( key == "MESSAGES" ) { CollectionStatistics s = collection.statistics(); s.setCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "UNSEEN" ) { CollectionStatistics s = collection.statistics(); s.setUnreadCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "SIZE" ) { CollectionStatistics s = collection.statistics(); s.setSize( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "CACHEPOLICY" ) { CachePolicy policy; ProtocolHelper::parseCachePolicy( value, policy ); collection.setCachePolicy( policy ); + } else if ( key == "ANCESTORS" ) { + QList ancestors; + ImapParser::parseParenthesizedList( value, ancestors ); + Collection* currentCol = &collection; + foreach ( const QByteArray uidRidPair, ancestors ) { + QList parentIds; + ImapParser::parseParenthesizedList( uidRidPair, parentIds ); + if ( parentIds.size() != 2 ) + break; + const Collection::Id uid = parentIds.at( 0 ).toLongLong(); + const QString rid = QString::fromUtf8( parentIds.at( 1 ) ); + if ( uid == Collection::root().id() ) { + currentCol->setParentCollection( Collection::root() ); + break; + } + currentCol->parentCollection().setId( uid ); + currentCol->parentCollection().setRemoteId( rid ); + currentCol = ¤tCol->parentCollection(); + } } else { Attribute* attr = AttributeFactory::createAttribute( key ); Q_ASSERT( attr ); attr->deserialize( value ); collection.addAttribute( attr ); } } return pos; } QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns ) { QList l; foreach ( const Attribute *attr, entity.attributes() ) { l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() ); l << ImapParser::quote( attr->serialized() ); } return ImapParser::join( l, " " ); } QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version ) { const QByteArray versionString( version != 0 ? '[' + QByteArray::number( version ) + ']' : "" ); switch ( ns ) { case PartGlobal: return label + versionString; case PartPayload: return "PLD:" + label + versionString; case PartAttribute: return "ATR:" + label + versionString; default: Q_ASSERT( false ); } return QByteArray(); } QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns ) { if ( data.startsWith( "PLD:" ) ) { //krazy:exclude=strings ns = PartPayload; return data.mid( 4 ); } else if ( data.startsWith( "ATR:" ) ) { //krazy:exclude=strings ns = PartAttribute; return data.mid( 4 ); } else { ns = PartGlobal; return data; } } QByteArray ProtocolHelper::itemSetToByteArray( const Item::List &_items, const QByteArray &command ) { if ( _items.isEmpty() ) throw Exception( "No items specified" ); Item::List items( _items ); QByteArray rv; std::sort( items.begin(), items.end(), boost::bind( &Item::id, _1 ) < boost::bind( &Item::id, _2 ) ); if ( items.first().isValid() ) { // all items have a uid set rv += " " AKONADI_CMD_UID " "; rv += command; rv += ' '; QList uids; foreach ( const Item &item, items ) uids << item.id(); ImapSet set; set.add( uids ); rv += set.toImapSequenceSet(); } else { // check if all items have a remote id QList rids; foreach ( const Item &item, items ) { if ( item.remoteId().isEmpty() ) throw Exception( i18n( "No remote identifier specified" ) ); rids << ImapParser::quote( item.remoteId().toUtf8() ); } rv += " " AKONADI_CMD_RID " "; rv += command; rv += " ("; rv += ImapParser::join( rids, " " ); rv += ')'; } return rv; } diff --git a/akonadi/selectionproxymodel.h b/akonadi/selectionproxymodel.h index 7c288fe35..b45036fbb 100644 --- a/akonadi/selectionproxymodel.h +++ b/akonadi/selectionproxymodel.h @@ -1,86 +1,86 @@ /* Copyright (c) 2009 Stephen Kelly 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_SELECTIONPROXYMODEL_H #define AKONADI_SELECTIONPROXYMODEL_H #include "akonadi_export.h" #include #include namespace Akonadi { class SelectionProxyModelPrivate; /** * @short A proxy model that provides data depending on the selection of a view. * * @author Stephen Kelly * @since 4.4 */ class AKONADI_EXPORT SelectionProxyModel : public KSelectionProxyModel { Q_OBJECT public: /** * Creates a new selection proxy model. * * @param selectionModel The selection model to work on. * @param parent The parent object. */ explicit SelectionProxyModel( QItemSelectionModel *selectionModel, QObject *parent = 0 ); /** * Destroys the selection proxy model. */ virtual ~SelectionProxyModel(); /** * Sets the header @p set that shall be used by the proxy model. * * \s EntityTreeModel::HeaderGroup */ void setHeaderSet( int set ); /** * Returns the header set used by the proxy model. */ int headerSet() const; // QAbstractProxyModel does not proxy all methods... virtual bool dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ); virtual QMimeData* mimeData( const QModelIndexList & indexes ) const; virtual QStringList mimeTypes() const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; private: //@cond PRIVATE Q_DECLARE_PRIVATE( SelectionProxyModel ) - SelectionProxyModelPrivate *d_ptr; + SelectionProxyModelPrivate * const d_ptr; //@endcond }; } #endif diff --git a/akonadi/session_p.h b/akonadi/session_p.h index 7565767e5..cfed0e56a 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 16; } + static int minimumProtocolVersion() { return 17; } 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/collectionsynctest.cpp b/akonadi/tests/collectionsynctest.cpp index 74851db97..9697f195e 100644 --- a/akonadi/tests/collectionsynctest.cpp +++ b/akonadi/tests/collectionsynctest.cpp @@ -1,260 +1,261 @@ /* Copyright (c) 2009 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 "test_utils.h" #include #include #include #include #include #include #include "../akonadi/collectionsync.cpp" #include #include #include #include using namespace Akonadi; Q_DECLARE_METATYPE( KJob* ) class CollectionSyncTest : public QObject { Q_OBJECT private: Collection::List fetchCollections( const QString &res ) { CollectionFetchJob *fetch = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this ); fetch->fetchScope().setResource( res ); + fetch->fetchScope().setAncestorRetrieval( CollectionFetchScope::All ); Q_ASSERT( fetch->exec() ); Q_ASSERT( !fetch->collections().isEmpty() ); return fetch->collections(); } void makeTestData() { QTest::addColumn( "hierarchicalRIDs" ); QTest::addColumn( "resource" ); QTest::newRow( "akonadi_knut_resource_0 global RID" ) << false << "akonadi_knut_resource_0"; QTest::newRow( "akonadi_knut_resource_1 global RID" ) << false << "akonadi_knut_resource_1"; QTest::newRow( "akonadi_knut_resource_2 global RID" ) << false << "akonadi_knut_resource_2"; -/* QTest::newRow( "akonadi_knut_resource_0 hierarchical RID" ) << true << "akonadi_knut_resource_0"; + QTest::newRow( "akonadi_knut_resource_0 hierarchical RID" ) << true << "akonadi_knut_resource_0"; QTest::newRow( "akonadi_knut_resource_1 hierarchical RID" ) << true << "akonadi_knut_resource_1"; - QTest::newRow( "akonadi_knut_resource_2 hierarchical RID" ) << true << "akonadi_knut_resource_2";*/ + QTest::newRow( "akonadi_knut_resource_2 hierarchical RID" ) << true << "akonadi_knut_resource_2"; } private slots: void initTestCase() { Control::start(); qRegisterMetaType(); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void testFullSync_data() { makeTestData(); } void testFullSync() { QFETCH( bool, hierarchicalRIDs ); QFETCH( QString, resource ); Collection::List origCols = fetchCollections( resource ); CollectionSync* syncer = new CollectionSync( resource, this ); syncer->setHierarchicalRemoteIds( hierarchicalRIDs ); syncer->setRemoteCollections( origCols ); AKVERIFYEXEC( syncer ); Collection::List resultCols = fetchCollections( resource ); QCOMPARE( resultCols.count(), origCols.count() ); } void testFullStreamingSync_data() { makeTestData(); } void testFullStreamingSync() { QFETCH( bool, hierarchicalRIDs ); QFETCH( QString, resource ); Collection::List origCols = fetchCollections( resource ); CollectionSync* syncer = new CollectionSync( resource, this ); syncer->setHierarchicalRemoteIds( hierarchicalRIDs ); syncer->setAutoDelete( false ); QSignalSpy spy( syncer, SIGNAL(result(KJob*)) ); QVERIFY( spy.isValid() ); syncer->setStreamingEnabled( true ); QTest::qWait( 10 ); QCOMPARE( spy.count(), 0 ); for ( int i = 0; i < origCols.count(); ++i ) { Collection::List l; l << origCols[i]; syncer->setRemoteCollections( l ); if ( i < origCols.count() - 1 ) QTest::qWait( 10 ); // enter the event loop so itemsync actually can do something QCOMPARE( spy.count(), 0 ); } syncer->retrievalDone(); QTest::qWait( 1000 ); // let it finish its job QCOMPARE( spy.count(), 1 ); KJob *job = spy.at( 0 ).at( 0 ).value(); QCOMPARE( job, syncer ); QCOMPARE( job->errorText(), QString() ); QCOMPARE( job->error(), 0 ); Collection::List resultCols = fetchCollections( resource ); QCOMPARE( resultCols.count(), origCols.count() ); delete syncer; } void testIncrementalSync_data() { makeTestData(); } void testIncrementalSync() { QFETCH( bool, hierarchicalRIDs ); QFETCH( QString, resource ); if ( resource == QLatin1String( "akonadi_knut_resource_2" ) ) QSKIP( "test requires more than one collection", SkipSingle ); Collection::List origCols = fetchCollections( resource ); CollectionSync* syncer = new CollectionSync( resource, this ); syncer->setHierarchicalRemoteIds( hierarchicalRIDs ); syncer->setRemoteCollections( origCols, Collection::List() ); AKVERIFYEXEC( syncer ); Collection::List resultCols = fetchCollections( resource ); QCOMPARE( resultCols.count(), origCols.count() ); Collection::List delCols; delCols << resultCols.front(); resultCols.pop_front(); // ### not implemented yet I guess #if 0 Collection colWithOnlyRemoteId; colWithOnlyRemoteId.setRemoteId( resultCols.front().remoteId() ); delCols << colWithOnlyRemoteId; resultCols.pop_front(); #endif #if 0 // ### should this work? Collection colWithRandomRemoteId; colWithRandomRemoteId.setRemoteId( KRandom::randomString( 100 ) ); delCols << colWithRandomRemoteId; #endif syncer = new CollectionSync( resource, this ); syncer->setRemoteCollections( resultCols, delCols ); AKVERIFYEXEC( syncer ); Collection::List resultCols2 = fetchCollections( resource ); QCOMPARE( resultCols2.count(), resultCols.count() ); } void testIncrementalStreamingSync_data() { makeTestData(); } void testIncrementalStreamingSync() { QFETCH( bool, hierarchicalRIDs ); QFETCH( QString, resource ); Collection::List origCols = fetchCollections( resource ); CollectionSync* syncer = new CollectionSync( resource, this ); syncer->setHierarchicalRemoteIds( hierarchicalRIDs ); syncer->setAutoDelete( false ); QSignalSpy spy( syncer, SIGNAL(result(KJob*)) ); QVERIFY( spy.isValid() ); syncer->setStreamingEnabled( true ); QTest::qWait( 10 ); QCOMPARE( spy.count(), 0 ); for ( int i = 0; i < origCols.count(); ++i ) { Collection::List l; l << origCols[i]; syncer->setRemoteCollections( l, Collection::List() ); if ( i < origCols.count() - 1 ) QTest::qWait( 10 ); // enter the event loop so itemsync actually can do something QCOMPARE( spy.count(), 0 ); } syncer->retrievalDone(); QTest::qWait( 1000 ); // let it finish its job QCOMPARE( spy.count(), 1 ); KJob *job = spy.at( 0 ).at( 0 ).value(); QCOMPARE( job, syncer ); QCOMPARE( job->errorText(), QString() ); QCOMPARE( job->error(), 0 ); Collection::List resultCols = fetchCollections( resource ); QCOMPARE( resultCols.count(), origCols.count() ); delete syncer; } void testEmptyIncrementalSync_data() { makeTestData(); } void testEmptyIncrementalSync() { QFETCH( bool, hierarchicalRIDs ); QFETCH( QString, resource ); Collection::List origCols = fetchCollections( resource ); CollectionSync* syncer = new CollectionSync( resource, this ); syncer->setHierarchicalRemoteIds( hierarchicalRIDs ); syncer->setRemoteCollections( Collection::List(), Collection::List() ); AKVERIFYEXEC( syncer ); Collection::List resultCols = fetchCollections( resource ); QCOMPARE( resultCols.count(), origCols.count() ); } }; QTEST_AKONADIMAIN( CollectionSyncTest, NoGUI ) #include "collectionsynctest.moc" diff --git a/akonadi/tests/protocolhelpertest.cpp b/akonadi/tests/protocolhelpertest.cpp index 951de3761..b595b0e2c 100644 --- a/akonadi/tests/protocolhelpertest.cpp +++ b/akonadi/tests/protocolhelpertest.cpp @@ -1,71 +1,116 @@ /* Copyright (c) 2009 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 "test_utils.h" #include "protocolhelper.cpp" using namespace Akonadi; class ProtocolHelperTest : public QObject { Q_OBJECT private slots: void testItemSetToByteArray_data() { QTest::addColumn( "items" ); QTest::addColumn( "result" ); QTest::addColumn( "shouldThrow" ); Item u1; u1.setId( 1 ); Item u2; u2.setId( 2 ); Item u3; u3.setId( 3 ); Item r1; r1.setRemoteId( "A" ); Item r2; r2.setRemoteId( "B" ); QTest::newRow( "empty" ) << Item::List() << QByteArray() << true; QTest::newRow( "single uid" ) << (Item::List() << u1) << QByteArray( " UID CMD 1" ) << false; QTest::newRow( "multi uid" ) << (Item::List() << u1 << u3) << QByteArray( " UID CMD 1,3" ) << false; QTest::newRow( "block uid" ) << (Item::List() << u1 << u2 << u3) << QByteArray( " UID CMD 1:3" ) << false; QTest::newRow( "single rid" ) << (Item::List() << r1) << QByteArray( " RID CMD (\"A\")" ) << false; QTest::newRow( "multi rid" ) << (Item::List() << r1 << r2) << QByteArray( " RID CMD (\"A\" \"B\")" ) << false; QTest::newRow( "invalid" ) << (Item::List() << Item()) << QByteArray() << true; QTest::newRow( "mixed" ) << (Item::List() << u1 << r1) << QByteArray() << true; } void testItemSetToByteArray() { QFETCH( Item::List, items ); QFETCH( QByteArray, result ); QFETCH( bool, shouldThrow ); bool didThrow = false; try { const QByteArray r = ProtocolHelper::itemSetToByteArray( items, "CMD" ); QCOMPARE( r, result ); } catch ( const std::exception &e ) { qDebug() << e.what(); didThrow = true; } QCOMPARE( didThrow, shouldThrow ); } + + void testCollectionParsing_data() + { + QTest::addColumn( "input" ); + QTest::addColumn( "collection" ); + + const QByteArray b1 = "2 1 (REMOTEID \"r2\" NAME \"n2\")"; + Collection c1; + c1.setId( 2 ); + c1.setRemoteId( "r2" ); + c1.parentCollection().setId( 1 ); + c1.setName( "n2" ); + QTest::newRow( "no ancestors" ) << b1 << c1; + + const QByteArray b2 = "3 2 (REMOTEID \"r3\" ANCESTORS ((2 \"r2\") (1 \"r1\") (0 \"\")))"; + Collection c2; + c2.setId( 3 ); + c2.setRemoteId( "r3" ); + c2.parentCollection().setId( 2 ); + c2.parentCollection().setRemoteId( "r2" ); + c2.parentCollection().parentCollection().setId( 1 ); + c2.parentCollection().parentCollection().setRemoteId( "r1" ); + c2.parentCollection().parentCollection().setParentCollection( Collection::root() ); + QTest::newRow( "ancestors" ) << b2 << c2; + } + + void testCollectionParsing() + { + QFETCH( QByteArray, input ); + QFETCH( Collection, collection ); + + Collection parsedCollection; + ProtocolHelper::parseCollection( input, parsedCollection ); + + QCOMPARE( parsedCollection.name(), collection.name() ); + + while ( collection.isValid() || parsedCollection.isValid() ) { + QCOMPARE( parsedCollection.id(), collection.id() ); + QCOMPARE( parsedCollection.remoteId(), collection.remoteId() ); + const Collection p1( parsedCollection.parentCollection() ); + const Collection p2( collection.parentCollection() ); + parsedCollection = p1; + collection = p2; + } + } }; QTEST_KDEMAIN( ProtocolHelperTest, NoGUI ) #include "protocolhelpertest.moc"