diff --git a/akonadi/entitycache_p.h b/akonadi/entitycache_p.h index d4e167755..961168473 100644 --- a/akonadi/entitycache_p.h +++ b/akonadi/entitycache_p.h @@ -1,214 +1,213 @@ /* 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_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include #include #include #include #include #include #include #include #include #include class KJob; namespace Akonadi { /** @internal QObject part of EntityCache. */ class EntityCacheBase : public QObject { Q_OBJECT public: - EntityCacheBase (QObject * parent = 0); + explicit EntityCacheBase (QObject * parent = 0); signals: void dataAvailable(); private slots: virtual void fetchResult( KJob* job ) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending( false ), invalid( false ) {} EntityCacheNode( typename T::Id id ) : entity( T( id ) ), pending( true ), invalid( false ) {} T entity; bool pending; bool invalid; }; } Q_DECLARE_METATYPE( Akonadi::EntityCacheNode* ) Q_DECLARE_METATYPE( Akonadi::EntityCacheNode* ) namespace Akonadi { /** * @internal * A in-memory FIFO cache for a small amount of Entity objects. */ template class EntityCache : public EntityCacheBase { public: - EntityCache( int maxCapacity, QObject *parent = 0 ) : + explicit EntityCache( int maxCapacity, QObject *parent = 0 ) : EntityCacheBase( parent ), mCapacity( maxCapacity ) {} ~EntityCache() { qDeleteAll( mCache ); } /** Object is available in the cache and can be retrieved. */ bool isCached( typename T::Id id ) const { EntityCacheNode* node = cacheNodeForId( id ); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested( typename T::Id id ) const { return cacheNodeForId( id ); } T retrieve( typename T::Id id ) const { Q_ASSERT( isCached( id ) ); - // TODO: return T() for invalid entries - return cacheNodeForId( id )->entity; + EntityCacheNode* node = cacheNodeForId( id ); + if ( !node->invalid ) + return node->entity; + return T(); } void invalidate( typename T::Id id ) { EntityCacheNode* node = cacheNodeForId( id ); if ( node ) node->invalid = true; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ void request( typename T::Id id, const FetchScope &scope ) { Q_ASSERT( !isRequested( id ) ); shrinkCache(); EntityCacheNode *node = new EntityCacheNode( id ); - FetchJob* job = new FetchJob( T( id ), this ); -// job->setFetchScope( scope ); // FIXME: ItemFetchJob is broken here! + FetchJob* job = createFetchJob( id ); + job->setFetchScope( scope ); job->setProperty( "EntityCacheNode", QVariant::fromValue*>( node ) ); connect( job, SIGNAL(result(KJob*)), SLOT(fetchResult(KJob*)) ); mCache.enqueue( node ); } private: EntityCacheNode* cacheNodeForId( typename T::Id id ) const { for ( typename QQueue*>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it ) { if ( (*it)->entity.id() == id ) return *it; } return 0; } void fetchResult( KJob* job ) { EntityCacheNode *node = job->property( "EntityCacheNode" ).value*>(); Q_ASSERT( node ); typename T::Id id = node->entity.id(); node->pending = false; extractResult( node, job ); if ( node->entity.id() != id ) { // make sure we find this node again if something went wrong here... kWarning() << "Something went very wrong..."; node->entity.setId( id ); node->invalid = true; } emit dataAvailable(); } - void extractResult( EntityCacheNode* node, KJob* job ); + void extractResult( EntityCacheNode* node, KJob* job ) const; + + inline FetchJob* createFetchJob( typename T::Id id ) + { + return new FetchJob( T( id ), this ); + } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while ( mCache.size() >= mCapacity && !mCache.first()->pending ) - mCache.dequeue(); + delete mCache.dequeue(); } private: QQueue*> mCache; int mCapacity; }; -// FIXME fix CollectionFetchJob instead! -template<> void EntityCache::request( Collection::Id id, const CollectionFetchScope &scope ) -{ - Q_ASSERT( !isRequested( id ) ); - shrinkCache(); - EntityCacheNode *node = new EntityCacheNode( id ); - CollectionFetchJob* job = new CollectionFetchJob( Collection( id ), CollectionFetchJob::Base, this ); - job->setFetchScope( scope ); - job->setProperty( "EntityCacheNode", QVariant::fromValue*>( node ) ); - connect( job, SIGNAL(result(KJob*)), SLOT(fetchResult(KJob*)) ); - mCache.enqueue( node ); -} - -template<> void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) +template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { CollectionFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->collections().isEmpty() ) node->entity = Collection(); else node->entity = fetch->collections().first(); } -template<> void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) +template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { ItemFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->items().isEmpty() ) node->entity = Item(); else node->entity = fetch->items().first(); } +template<> inline CollectionFetchJob* EntityCache::createFetchJob( Collection::Id id ) +{ + return new CollectionFetchJob( Collection( id ), CollectionFetchJob::Base, this ); +} + typedef EntityCache CollectionCache; typedef EntityCache ItemCache; } #endif diff --git a/akonadi/tests/entitycachetest.cpp b/akonadi/tests/entitycachetest.cpp index bbae7c894..9b2ee026f 100644 --- a/akonadi/tests/entitycachetest.cpp +++ b/akonadi/tests/entitycachetest.cpp @@ -1,82 +1,98 @@ /* 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 "entitycache.cpp" #include #include using namespace Akonadi; class EntityCacheTest : public QObject { Q_OBJECT private: template void testCache() { EntityCache cache( 2 ); QSignalSpy spy( &cache, SIGNAL(dataAvailable()) ); QVERIFY( spy.isValid() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); cache.request( 1, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 1 ); QVERIFY( cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); const T e1 = cache.retrieve( 1 ); QCOMPARE( e1.id(), 1ll ); spy.clear(); cache.request( 2, FetchScope() ); cache.request( 3, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); QVERIFY( cache.isRequested( 2 ) ); QVERIFY( cache.isRequested( 3 ) ); + + cache.invalidate( 2 ); + + QTest::qWait( 1000 ); + QCOMPARE( spy.count(), 2 ); + QVERIFY( cache.isCached( 2 ) ); + QVERIFY( cache.isCached( 3 ) ); + + const T e2 = cache.retrieve( 2 ); + const T e3a = cache.retrieve( 3 ); + QCOMPARE( e3a.id(), 3ll ); + QVERIFY( !e2.isValid() ); + + cache.invalidate( 3 ); + const T e3b = cache.retrieve( 3 ); + QVERIFY( !e3b.isValid() ); } private slots: void testCacheGeneric_data() { QTest::addColumn( "collection" ); QTest::newRow( "collection" ) << true; QTest::newRow( "item" ) << false; } void testCacheGeneric() { QFETCH( bool, collection ); if ( collection ) testCache(); else testCache(); } }; QTEST_AKONADIMAIN( EntityCacheTest, NoGUI ) #include "entitycachetest.moc"