diff --git a/akonadi/collectionfetchscope.h b/akonadi/collectionfetchscope.h index facd155cc..bdb4fd179 100644 --- a/akonadi/collectionfetchscope.h +++ b/akonadi/collectionfetchscope.h @@ -1,186 +1,186 @@ /* Copyright (c) 2008 Kevin Krammer 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. */ #ifndef AKONADI_COLLECTIONFETCHSCOPE_H #define AKONADI_COLLECTIONFETCHSCOPE_H #include "akonadi_export.h" #include class QStringList; namespace Akonadi { class CollectionFetchScopePrivate; /** * @short Specifies which parts of a collection should be fetched from the Akonadi storage. * * When collections are fetched from server either by using CollectionFetchJob explicitly or * when it is being used internally by other classes, e.g. Akonadi::Monitor, the scope * of the fetch operation can be tailored to the application's current needs. * * There are two supported ways of changing the currently active CollectionFetchScope * of classes: * - in-place: modify the CollectionFetchScope object the other class holds as a member * - replace: replace the other class' member with a new scope object * * Example: modifying an CollectionFetchJob's scope @c in-place * @code * Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob( collection ); * job->fetchScope().setIncludeUnsubscribed( true ); * @endcode * * Example: @c replacing an CollectionFetchJob's scope * @code * Akonadi::CollectionFetchScope scope; * scope.setIncludeUnsubscribed( true ); * * Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob( collection ); * job->setFetchScope( scope ); * @endcode * * This class is implicitly shared. * * @author Volker Krause * @since 4.4 */ class AKONADI_EXPORT CollectionFetchScope { public: /** * Describes the ancestor retrieval depth. */ enum AncestorRetrieval { None, ///< No ancestor retrieval at all (the default) Parent, ///< Only retrieve the immediate parent collection All ///< Retrieve all ancestors, up to Collection::root() }; /** * Creates an empty collection fetch scope. * * Using an empty scope will only fetch the very basic meta data of collections, * e.g. local id, remote id and content mimetypes. */ CollectionFetchScope(); /** * Creates a new collection fetch scope from an @p other. */ CollectionFetchScope( const CollectionFetchScope &other ); /** * Destroys the collection fetch scope. */ ~CollectionFetchScope(); /** * Assigns the @p other to this scope and returns a reference to this scope. */ CollectionFetchScope &operator=( const CollectionFetchScope &other ); /** * Returns whether unsubscribed collection should be included. * * @see setIncludeUnsubscribed() */ bool includeUnubscribed() const; /** - * Sets wether unsubscribed collections should be included in the collection listing. + * Sets whether unsubscribed collections should be included in the collection listing. * * @param include @c true to include unsubscribed collections, @c false otherwise (the default). */ void setIncludeUnsubscribed( bool include ); /** * Returns whether collection statistics should be included in the retrieved results. * * @see setIncludeStatistics() */ bool includeStatistics() const; /** * Sets whether collection statistics should be included in the retrieved results. * * @param include @c true to include collction statistics, @c false otherwise (the default). */ void setIncludeStatistics( bool include ); /** * Returns the resource identifier that is used as filter. * * @see setResource() */ QString resource() const; /** * Sets a resource filter, that is only collections owned by the specified resource are * retrieved. * * @param resource The resource identifier. */ void setResource( const QString &resource ); /** * Sets a content mimetypes filter, that is only collections that contain at least one of the * given mimetypes (or their parents) are retrieved. * * @param mimeTypes A list of mime types */ void setContentMimeTypes( const QStringList &mimeTypes ); /** * Returns the content mimetypes filter. * * @see setContentMimeTypes() */ QStringList contentMimeTypes() const; /** * Sets how many levels of ancestor collections should be included in the retrieval. * * @param ancestorDepth The desired ancestor retrieval depth. */ void setAncestorRetrieval( AncestorRetrieval ancestorDepth ); /** * Returns the ancestor retrieval depth. * * @see setAncestorRetrieval() */ AncestorRetrieval ancestorRetrieval() const; /** * Returns @c true if there is nothing to fetch. */ bool isEmpty() const; private: //@cond PRIVATE QSharedDataPointer d; //@endcond }; } #endif diff --git a/akonadi/collectionsync_p.h b/akonadi/collectionsync_p.h index 75ed693a1..38a6a0073 100644 --- a/akonadi/collectionsync_p.h +++ b/akonadi/collectionsync_p.h @@ -1,122 +1,122 @@ /* 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. */ #ifndef AKONADI_COLLECTIONSYNC_P_H #define AKONADI_COLLECTIONSYNC_P_H #include #include namespace Akonadi { /** @internal Syncs remote and local collections. Basic terminology: - "local": The current state in the Akonadi server - "remote": The state in the backend, which is also the state the Akonadi server is supposed to have afterwards. There are three options to influence the way syncing is done: - Streaming vs. complete delivery: If streaming is enabled remote collections do not need to be delivered in a single batch but can be delivered in multiple chunks. This improves performance but requires an explicit notification when delivery has been completed. - Incremental vs. non-incremental: In the incremental case only remote changes since the last sync have to be delivered, in the non-incremental mode the full remote state has to be provided. The first is obviously the preferred way, but requires support by the backend. - Hierarchical vs. global RIDs: The first requires RIDs to be unique per parent collection, the second one requires globally unique RIDs (per resource). Those have different advantages and disadvantages, esp. regarding moving. Which one to chose mostly depends on what the backend provides in this regard. */ class CollectionSync : public TransactionSequence { Q_OBJECT public: /** Creates a new collection synchronzier. @param resourceId The identifier of the resource we are syncing. @param parent The parent object. */ explicit CollectionSync( const QString &resourceId, QObject *parent = 0 ); /** Destroys this job. */ ~CollectionSync(); /** Sets the result of a full remote collection listing. @param remoteCollections A list of collections. Important: All of these need a unique remote identifier and parent remote identifier. */ void setRemoteCollections( const Collection::List &remoteCollections ); /** Sets the result of an incremental remote collection listing. @param changedCollections A list of remotely added or changed collections. @param removedCollections A lost of remotely deleted collections. */ void setRemoteCollections( const Collection::List &changedCollections, const Collection::List &removedCollections ); /** Enables streaming, that is not all collections are delivered at once. Use setRemoteCollections() multiple times when streaming is enabled and call retrievalDone() when all collections have been retrieved. Must be called before the first call to setRemoteCollections(). */ void setStreamingEnabled( bool streaming ); /** Indicate that all collections have been retrieved in streaming mode. */ void retrievalDone(); /** - Indicate whether the resource supplies collections with hierachical or global - remote identifiers. @c false by default. + Indicate whether the resource supplies collections with hierarchical or + global remote identifiers. @c false by default. Must be called before the first call to setRemoteCollections(). */ void setHierarchicalRemoteIds( bool hierarchical ); protected: void doStart(); private: class Private; Private* const d; Q_PRIVATE_SLOT( d, void localCollectionsReceived( const Akonadi::Collection::List &localCols ) ) Q_PRIVATE_SLOT( d, void localCollectionFetchResult( KJob* job ) ) Q_PRIVATE_SLOT( d, void updateLocalCollectionResult(KJob* job) ) Q_PRIVATE_SLOT( d, void createLocalCollectionResult(KJob* job) ) Q_PRIVATE_SLOT( d, void deleteLocalCollectionsResult(KJob* job) ) }; } #endif diff --git a/akonadi/entitytreemodel_p.cpp b/akonadi/entitytreemodel_p.cpp index 047aee00b..9bfe88ec3 100644 --- a/akonadi/entitytreemodel_p.cpp +++ b/akonadi/entitytreemodel_p.cpp @@ -1,765 +1,765 @@ /* 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 ) { Q_Q( EntityTreeModel ); // kDebug() << parent.remoteId(); Akonadi::ItemFetchJob *itemJob = new Akonadi::ItemFetchJob( parent, m_session ); itemJob->setFetchScope( m_monitor->itemFetchScope() ); // ### 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() ) ); q->connect( itemJob, SIGNAL( itemsReceived( const Akonadi::Item::List& ) ), q, SLOT( itemsFetched( const Akonadi::Item::List& ) ) ); q->connect( itemJob, SIGNAL( result( KJob* ) ), q, SLOT( fetchJobDone( KJob* ) ) ); } 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)) { Q_ASSERT("Something went very wrong" == "false"); } - m_pendingChildCollections.remove( colId ); + m_pendingChildCollections.remove( colId ); } - + it.remove(); } else { m_pendingCollections.insert( colId, col ); 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 recieves collections. + // 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/preprocessorbase.h b/akonadi/preprocessorbase.h index 097ead2b4..5b7c06693 100644 --- a/akonadi/preprocessorbase.h +++ b/akonadi/preprocessorbase.h @@ -1,185 +1,185 @@ /****************************************************************************** * * 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 AKONADI_PREPROCESSORBASE_H #define AKONADI_PREPROCESSORBASE_H #include "akonadi_export.h" #include #include #include class PreprocessorAdaptor; namespace Akonadi { class PreprocessorBasePrivate; /** * @short The base class for all Akonadi preprocessor agents. * * This class should be used as a base class by all preprocessor agents * since it encapsulates large parts of the protocol between * preprocessor agent, agent manager and the Akonadi storage. * * Preprocessor agents are special agents that are informed about newly * added items before any other agents. This allows them to do filtering * on the items or any other task that shall be done before the new item * is visible in the Akonadi storage system. * * The method all the preprocessors must implement is processItem(). * * @author Szymon Stefanek * @since 4.4 */ class AKONADI_EXPORT PreprocessorBase : public AgentBase { friend class PreprocessorAdaptor; Q_OBJECT public: /** * Describes the possible return values of the processItem() method. */ enum ProcessingResult { /** - * Processing completed succesfully for this item. + * Processing completed successfully for this item. * The Akonadi server will push in a new item when it's available. */ ProcessingCompleted, /** * Processing was delayed to a later stage. * This must be returned when implementing asynchronous preprocessing. * * If this value is returned, terminateProcessing() has to be called * when processing is done. */ ProcessingDelayed, /** * Processing for this item failed (and the failure is unrecoverable). * The Akonadi server will push in a new item when it's available, * after possibly logging the failure. */ ProcessingFailed, /** * Processing for this item was refused. This is very * similar to ProcessingFailed above but additionally remarks * that the item that the Akonadi server pushed in wasn't * meant for this Preprocessor. * The Akonadi server will push in a new item when it's available, * after possibly logging the failure and maybe taking some additional action. */ ProcessingRefused }; /** * This method must be implemented by every preprocessor subclass. * * It must realize the preprocessing of the item with the specified itemId, * which is actually in the collection with collectionId and has the specified mimetype. * * The Akonadi server will push in for preprocessing any newly created item: - * it's your responsability to decide if you want to process the item or not. + * it's your responsibility to decide if you want to process the item or not. * * The method should return ProcessingCompleted on success, ProcessingDelayed * if processing is implemented asynchronously and * ProcessingRefused or ProcessingFailed if the processing * didn't complete. * * If your operation is asynchronous then you should also * connect to the abortRequested() signal and handle it * appropriately (as the server MAY abort your async job * if it decides that it's taking too long). */ virtual ProcessingResult processItem( Item::Id itemId, Collection::Id collectionId, const QString &mimeType ) = 0; /** * This method must be called if processing is implemented asynchronously. * * You should call it when you have completed the processing * or if an abortRequest() signal arrives (and in this case you * will probably use ProcessingFailed as result). * * Valid values for @p result are ProcessingCompleted, * PocessingRefused and ProcessingFailed. Passing any * other value will lead to a runtime assertion. */ void terminateProcessing( ProcessingResult result ); Q_SIGNALS: /** * This signal is emitted to report item processing termination * to the Akonadi server. * * @note This signal is only for internal use. */ void itemProcessed( qlonglong id ); protected: /** * Creates a new preprocessor base agent. * * @param id The instance id of the preprocessor base agent. */ PreprocessorBase( const QString &id ); /** * Destroys the preprocessor base agent. */ virtual ~PreprocessorBase(); /** * This dbus method is called by the Akonadi server * in order to trigger the processing of an item. * * @note Do not call it manually! */ void beginProcessItem( qlonglong itemId, qlonglong collectionId, const QString &mimeType ); private: // dbus Preprocessor interface friend class ::PreprocessorAdaptor; Q_DECLARE_PRIVATE( PreprocessorBase ) }; // class PreprocessorBase } // namespace Akonadi #ifndef AKONADI_PREPROCESSOR_MAIN /** * Convenience Macro for the most common main() function for Akonadi preprocessors. */ #define AKONADI_PREPROCESSOR_MAIN( preProcessorClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::PreprocessorBase::init( argc, argv ); \ } #endif //!AKONADI_RESOURCE_MAIN #endif //!_PREPROCESSORBASE_H_