diff --git a/akonadi/collectionmodel_p.cpp b/akonadi/collectionmodel_p.cpp index f6254d359..d0b89fb07 100644 --- a/akonadi/collectionmodel_p.cpp +++ b/akonadi/collectionmodel_p.cpp @@ -1,333 +1,332 @@ /* Copyright (c) 2006 - 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. */ //@cond PRIVATE #include "collectionmodel_p.h" #include "collectionmodel.h" #include "collectionutils_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "monitor.h" #include "session.h" #include #include #include #include #include using namespace Akonadi; void CollectionModelPrivate::collectionRemoved( const Akonadi::Collection &collection ) { Q_Q( CollectionModel ); QModelIndex colIndex = indexForId( collection.id() ); if ( colIndex.isValid() ) { QModelIndex parentIndex = q->parent( colIndex ); // collection is still somewhere in the hierarchy removeRowFromModel( colIndex.row(), parentIndex ); } else { if ( collections.contains( collection.id() ) ) { // collection is orphan, ie. the parent has been removed already collections.remove( collection.id() ); childCollections.remove( collection.id() ); } } } void CollectionModelPrivate::collectionChanged( const Akonadi::Collection &collection ) { Q_Q( CollectionModel ); // What kind of change is it ? Collection::Id oldParentId = collections.value( collection.id() ).parent(); Collection::Id newParentId = collection.parent(); if ( newParentId != oldParentId && oldParentId >= 0 ) { // It's a move removeRowFromModel( indexForId( collections[ collection.id() ].id() ).row(), indexForId( oldParentId ) ); Collection newParent; if ( newParentId == Collection::root().id() ) newParent = Collection::root(); else newParent = collections.value( newParentId ); CollectionFetchJob *job = new CollectionFetchJob( newParent, CollectionFetchJob::Recursive, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( listDone( KJob* ) ) ); } else { // It's a simple change CollectionFetchJob *job = new CollectionFetchJob( collection, CollectionFetchJob::Base, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( listDone( KJob* ) ) ); } } void CollectionModelPrivate::updateDone( KJob *job ) { if ( job->error() ) { // TODO: handle job errors kWarning( 5250 ) << "Job error:" << job->errorString(); } else { CollectionStatisticsJob *csjob = static_cast( job ); Collection result = csjob->collection(); collectionStatisticsChanged( result.id(), csjob->statistics() ); } } void CollectionModelPrivate::collectionStatisticsChanged( Collection::Id collection, const Akonadi::CollectionStatistics &statistics ) { Q_Q( CollectionModel ); if ( !collections.contains( collection ) ) kWarning( 5250 ) << "Got statistics response for non-existing collection:" << collection; else { collections[ collection ].setStatistics( statistics ); Collection col = collections.value( collection ); QModelIndex startIndex = indexForId( col.id() ); QModelIndex endIndex = indexForId( col.id(), q->columnCount( q->parent( startIndex ) ) - 1 ); emit q->dataChanged( startIndex, endIndex ); } } void CollectionModelPrivate::listDone( KJob *job ) { if ( job->error() ) { kWarning( 5250 ) << "Job error: " << job->errorString() << endl; } } void CollectionModelPrivate::editDone( KJob * job ) { if ( job->error() ) { kWarning( 5250 ) << "Edit failed: " << job->errorString(); } } void CollectionModelPrivate::dropResult(KJob * job) { if ( job->error() ) { kWarning( 5250 ) << "Paste failed:" << job->errorString(); // TODO: error handling } } void CollectionModelPrivate::collectionsChanged( const Collection::List &cols ) { Q_Q( CollectionModel ); - foreach ( Collection col, cols ) - { + foreach ( Collection col, cols ) { //krazy:exclude=foreach non-const is needed here if ( collections.contains( col.id() ) ) { // If the collection is already known to the model, we simply update it... col.setStatistics( collections.value( col.id() ).statistics() ); collections[ col.id() ] = col; QModelIndex startIndex = indexForId( col.id() ); QModelIndex endIndex = indexForId( col.id(), q->columnCount( q->parent( startIndex ) ) - 1 ); emit q->dataChanged( startIndex, endIndex ); continue; } // ... otherwise we add it to the set of collections we need to handle. m_newChildCollections[ col.parent() ].append( col.id() ); m_newCollections.insert( col.id(), col ); } // Handle the collections in m_newChildCollections. If the collections // parent is already in the model, the collection can be added to the model. // Otherwise it is persisted until it has a valid parent in the model. int currentSize = m_newChildCollections.size(); int lastSize = -1; while ( currentSize > 0 ) { lastSize = currentSize; QMutableHashIterator< Collection::Id, QList< Collection::Id > > i( m_newChildCollections ); while ( i.hasNext() ) { i.next(); // the key is the parent of new collections. It may itself also be new, // but that will be handled later. Collection::Id colId = i.key(); QList< Collection::Id > newChildCols = i.value(); int newChildCount = newChildCols.size(); // if ( newChildCount == 0 ) // { // // Sanity check. // kDebug() << "No new child collections have been added to the collection:" << colId; // i.remove(); // currentSize--; // break; // } if ( collections.contains( colId ) || colId == Collection::root().id() ) { QModelIndex parentIndex = indexForId( colId ); int currentChildCount = childCollections.value( colId ).size(); q->beginInsertRows( parentIndex, currentChildCount, // Start index is at the end of existing collections. currentChildCount + newChildCount - 1 ); // End index is the result of the insertion. foreach( Collection::Id id, newChildCols ) { Collection c = m_newCollections.take( id ); collections.insert( id, c ); } childCollections[ colId ] << newChildCols; q->endInsertRows(); i.remove(); currentSize--; break; } } // We iterated through once without adding any more collections to the model. if ( currentSize == lastSize ) { // The remaining collections in the list do not have a valid parent in the model yet. They // might arrive in the next batch from the monitor, so they're still in m_newCollections // and m_newChildCollections. kDebug() << "Some collections did not have a parent in the model yet!"; break; } } } QModelIndex CollectionModelPrivate::indexForId( Collection::Id id, int column ) const { Q_Q( const CollectionModel ); if ( !collections.contains( id ) ) return QModelIndex(); Collection::Id parentId = collections.value( id ).parent(); // check if parent still exist or if this is an orphan collection if ( parentId != Collection::root().id() && !collections.contains( parentId ) ) return QModelIndex(); QList list = childCollections.value( parentId ); int row = list.indexOf( id ); if ( row >= 0 ) return q->createIndex( row, column, reinterpret_cast( collections.value( list.at(row) ).id() ) ); return QModelIndex(); } bool CollectionModelPrivate::removeRowFromModel( int row, const QModelIndex & parent ) { Q_Q( CollectionModel ); QList list; Collection parentCol; if ( parent.isValid() ) { parentCol = collections.value( parent.internalId() ); Q_ASSERT( parentCol.id() == parent.internalId() ); list = childCollections.value( parentCol.id() ); } else { parentCol = Collection::root(); list = childCollections.value( Collection::root().id() ); } if ( row < 0 || row >= list.size() ) { kWarning( 5250 ) << "Index out of bounds:" << row <<" parent:" << parentCol.id(); return false; } q->beginRemoveRows( parent, row, row ); Collection::Id delColId = list.takeAt( row ); foreach( Collection::Id childColId, childCollections[ delColId ] ) collections.remove( childColId ); collections.remove( delColId ); childCollections.remove( delColId ); // remove children of deleted collection childCollections.insert( parentCol.id(), list ); // update children of parent q->endRemoveRows(); return true; } bool CollectionModelPrivate::supportsContentType(const QModelIndex & index, const QStringList & contentTypes) { if ( !index.isValid() ) return false; Collection col = collections.value( index.internalId() ); Q_ASSERT( col.isValid() ); QStringList ct = col.contentMimeTypes(); foreach ( const QString &a, ct ) { if ( contentTypes.contains( a ) ) return true; } return false; } void CollectionModelPrivate::init() { Q_Q( CollectionModel ); session = new Session( QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-CollectionModel-") + QByteArray::number( qrand() ), q ); QTimer::singleShot( 0, q, SLOT(startFirstListJob()) ); // monitor collection changes monitor = new Monitor(); monitor->setCollectionMonitored( Collection::root() ); monitor->fetchCollection( true ); // ### Hack to get the kmail resource folder icons KIconLoader::global()->addAppDir( QLatin1String( "kmail" ) ); KIconLoader::global()->addAppDir( QLatin1String( "kdepim" ) ); // monitor collection changes q->connect( monitor, SIGNAL(collectionChanged(const Akonadi::Collection&)), q, SLOT(collectionChanged(const Akonadi::Collection&)) ); q->connect( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), q, SLOT(collectionChanged(Akonadi::Collection)) ); q->connect( monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), q, SLOT(collectionRemoved(Akonadi::Collection)) ); q->connect( monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), q, SLOT(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) ); } void CollectionModelPrivate::startFirstListJob() { Q_Q( CollectionModel ); // start a list job CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, session ); job->includeUnsubscribed( unsubscribed ); job->includeStatistics( fetchStatistics ); q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List)) ); q->connect( job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*)) ); } //@endcond diff --git a/akonadi/itemsync.cpp b/akonadi/itemsync.cpp index 378bc5721..328085082 100644 --- a/akonadi/itemsync.cpp +++ b/akonadi/itemsync.cpp @@ -1,397 +1,397 @@ /* Copyright (c) 2007 Tobias Koenig Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemsync.h" #include "collection.h" #include "item.h" #include "itemcreatejob.h" #include "itemdeletejob.h" #include "itemfetchjob.h" #include "itemmodifyjob.h" #include "transactionsequence.h" #include "itemfetchscope.h" #include #include using namespace Akonadi; /** * @internal */ class ItemSync::Private { public: Private( ItemSync *parent ) : q( parent ), mTransactionMode( Single ), mCurrentTransaction( 0 ), mTransactionJobs( 0 ), mPendingJobs( 0 ), mProgress( 0 ), mTotalItems( -1 ), mTotalItemsProcessed( 0 ), mStreaming( false ), mIncremental( false ), mLocalListDone( false ), mDeliveryDone( false ) { // we want to fetch all data by default mFetchScope.fetchFullPayload(); mFetchScope.fetchAllAttributes(); } void createLocalItem( const Item &item ); void checkDone(); void slotLocalListDone( KJob* ); void slotLocalChangeDone( KJob* ); void execute(); void processItems(); void deleteItems( const Item::List &items ); void slotTransactionResult( KJob *job ); Job* subjobParent() const; ItemSync *q; Collection mSyncCollection; QHash mLocalItemsById; QHash mLocalItemsByRemoteId; QSet mUnprocessedLocalItems; // transaction mode, TODO: make this public API? enum TransactionMode { Single, Chunkwise, None }; TransactionMode mTransactionMode; TransactionSequence *mCurrentTransaction; int mTransactionJobs; // fetch scope for initial item listing ItemFetchScope mFetchScope; // remote items Akonadi::Item::List mRemoteItems; // removed remote items Item::List mRemovedRemoteItems; // create counter int mPendingJobs; int mProgress; int mTotalItems; int mTotalItemsProcessed; bool mStreaming; bool mIncremental; bool mLocalListDone; bool mDeliveryDone; }; void ItemSync::Private::createLocalItem( const Item & item ) { mPendingJobs++; ItemCreateJob *create = new ItemCreateJob( item, mSyncCollection, subjobParent() ); q->connect( create, SIGNAL( result( KJob* ) ), q, SLOT( slotLocalChangeDone( KJob* ) ) ); } void ItemSync::Private::checkDone() { q->setProcessedAmount( KJob::Bytes, mProgress ); if ( mPendingJobs > 0 || !mDeliveryDone || mTransactionJobs > 0 ) return; q->emitResult(); } ItemSync::ItemSync( const Collection &collection, QObject *parent ) : Job( parent ), d( new Private( this ) ) { d->mSyncCollection = collection; } ItemSync::~ItemSync() { delete d; } void ItemSync::setFullSyncItems( const Item::List &items ) { Q_ASSERT( !d->mIncremental ); if ( !d->mStreaming ) d->mDeliveryDone = true; d->mRemoteItems += items; d->mTotalItemsProcessed += items.count(); kDebug() << "Received: " << items.count() << "In total: " << d->mTotalItemsProcessed << " Wanted: " << d->mTotalItems; setTotalAmount( KJob::Bytes, d->mTotalItemsProcessed ); if ( d->mTotalItemsProcessed == d->mTotalItems ) d->mDeliveryDone = true; d->execute(); } void ItemSync::setTotalItems( int amount ) { Q_ASSERT( !d->mIncremental ); Q_ASSERT( amount >= 0 ); setStreamingEnabled( true ); kDebug() << amount; d->mTotalItems = amount; setTotalAmount( KJob::Bytes, amount ); if ( d->mTotalItems == 0 ) { d->mDeliveryDone = true; d->execute(); } } void ItemSync::setIncrementalSyncItems( const Item::List &changedItems, const Item::List &removedItems ) { d->mIncremental = true; if ( !d->mStreaming ) d->mDeliveryDone = true; d->mRemoteItems += changedItems; d->mRemovedRemoteItems += removedItems; d->mTotalItemsProcessed += changedItems.count() + removedItems.count(); setTotalAmount( KJob::Bytes, d->mTotalItemsProcessed ); if ( d->mTotalItemsProcessed == d->mTotalItems ) d->mDeliveryDone = true; d->execute(); } void ItemSync::setFetchScope( ItemFetchScope &fetchScope ) { d->mFetchScope = fetchScope; } ItemFetchScope &ItemSync::fetchScope() { return d->mFetchScope; } void ItemSync::doStart() { ItemFetchJob* job = new ItemFetchJob( d->mSyncCollection, this ); job->setFetchScope( d->mFetchScope ); // we only can fetch parts already in the cache, otherwise this will deadlock job->fetchScope().setCacheOnly( true ); connect( job, SIGNAL( result( KJob* ) ), SLOT( slotLocalListDone( KJob* ) ) ); } bool ItemSync::updateItem( const Item &storedItem, Item &newItem ) { /* * We know that this item has changed (as it is part of the * incremental changed list), so we just put it into the * storage. */ if ( d->mIncremental ) return true; // Check whether the flags differ if ( storedItem.flags() != newItem.flags() ) { kDebug( 5250 ) << "Stored flags " << storedItem.flags() << "new flags " << newItem.flags(); return true; } // Check whether the new item contains unknown parts QSet missingParts = storedItem.loadedPayloadParts(); missingParts.subtract( newItem.loadedPayloadParts() ); if ( !missingParts.isEmpty() ) return true; // ### FIXME SLOW!!! // If the available part identifiers don't differ, check // whether the content of the payload differs if ( storedItem.payloadData() != newItem.payloadData() ) return true; // check if remote attributes have been changed foreach ( Attribute* attr, newItem.attributes() ) { if ( !storedItem.hasAttribute( attr->type() ) ) return true; if ( attr->serialized() != storedItem.attribute( attr->type() )->serialized() ) return true; } return false; } void ItemSync::Private::slotLocalListDone( KJob * job ) { if ( job->error() ) return; const Item::List list = static_cast( job )->items(); foreach ( const Item &item, list ) { mLocalItemsById.insert( item.id(), item ); mLocalItemsByRemoteId.insert( item.remoteId(), item ); mUnprocessedLocalItems.insert( item ); } mLocalListDone = true; execute(); } void ItemSync::Private::execute() { if ( !mLocalListDone ) return; if ( (mTransactionMode == Single && !mCurrentTransaction) || mTransactionMode == Chunkwise ) { ++mTransactionJobs; mCurrentTransaction = new TransactionSequence( q ); connect( mCurrentTransaction, SIGNAL(result(KJob*)), q, SLOT(slotTransactionResult(KJob*)) ); } processItems(); if ( !mDeliveryDone ) { if ( mTransactionMode == Chunkwise && mCurrentTransaction ) { mCurrentTransaction->commit(); mCurrentTransaction = 0; } return; } // removed if ( !mIncremental ) { mRemovedRemoteItems = mUnprocessedLocalItems.toList(); mUnprocessedLocalItems.clear(); } deleteItems( mRemovedRemoteItems ); mLocalItemsById.clear(); mLocalItemsByRemoteId.clear(); mRemovedRemoteItems.clear(); if ( mCurrentTransaction ) { mCurrentTransaction->commit(); mCurrentTransaction = 0; } checkDone(); } void ItemSync::Private::processItems() { // added / updated - foreach ( Item remoteItem, mRemoteItems ) { + foreach ( Item remoteItem, mRemoteItems ) { //krazy:exclude=foreach non-const is needed here #ifndef NDEBUG if ( remoteItem.remoteId().isEmpty() ) { kWarning( 5250 ) << "Item " << remoteItem.id() << " does not have a remote identifier"; } #endif Item localItem = mLocalItemsById.value( remoteItem.id() ); if ( !localItem.isValid() ) localItem = mLocalItemsByRemoteId.value( remoteItem.remoteId() ); mUnprocessedLocalItems.remove( localItem ); // missing locally if ( !localItem.isValid() ) { createLocalItem( remoteItem ); continue; } if ( q->updateItem( localItem, remoteItem ) ) { mPendingJobs++; remoteItem.setId( localItem.id() ); remoteItem.setRevision( localItem.revision() ); remoteItem.setSize( localItem.size() ); remoteItem.setRemoteId( localItem.remoteId() ); // in case someone clears remoteId by accident ItemModifyJob *mod = new ItemModifyJob( remoteItem, subjobParent() ); q->connect( mod, SIGNAL( result( KJob* ) ), q, SLOT( slotLocalChangeDone( KJob* ) ) ); } else { mProgress++; } } mRemoteItems.clear(); } void ItemSync::Private::deleteItems( const Item::List &items ) { foreach ( const Item &item, items ) { Item delItem( item ); if ( !item.isValid() ) { delItem = mLocalItemsByRemoteId.value( item.remoteId() ); } if ( !delItem.isValid() ) { #ifndef NDEBUG kWarning( 5250 ) << "Delete item (remoteeId=" << delItem.remoteId() << "mimeType=" << delItem.mimeType() << ") does not have a valid UID and no item with that remote ID exists either"; #endif continue; } mPendingJobs++; ItemDeleteJob *job = new ItemDeleteJob( delItem, subjobParent() ); q->connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotLocalChangeDone( KJob* ) ) ); } } void ItemSync::Private::slotLocalChangeDone( KJob * job ) { if ( job->error() ) return; mPendingJobs--; mProgress++; checkDone(); } void ItemSync::Private::slotTransactionResult( KJob *job ) { if ( job->error() ) return; --mTransactionJobs; if ( mCurrentTransaction == job ) mCurrentTransaction = 0; checkDone(); } Job * ItemSync::Private::subjobParent() const { if ( mCurrentTransaction && mTransactionMode != None ) return mCurrentTransaction; return q; } void ItemSync::setStreamingEnabled(bool enable) { d->mStreaming = enable; } void ItemSync::deliveryDone() { Q_ASSERT( d->mStreaming ); d->mDeliveryDone = true; d->execute(); } #include "itemsync.moc"