diff --git a/akonadi/entitycache_p.h b/akonadi/entitycache_p.h index 961168473..1f451c76f 100644 --- a/akonadi/entitycache_p.h +++ b/akonadi/entitycache_p.h @@ -1,213 +1,224 @@ /* 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: 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: 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 ); } + /** Returns the cached object if available, an empty instance otherwise. */ T retrieve( typename T::Id id ) const { - Q_ASSERT( isCached( id ) ); EntityCacheNode* node = cacheNodeForId( id ); - if ( !node->invalid ) + if ( node && !node->pending && !node->invalid ) return node->entity; return T(); } void invalidate( typename T::Id id ) { EntityCacheNode* node = cacheNodeForId( id ); if ( node ) node->invalid = true; } + /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ + bool ensureCached( typename T::Id id, const FetchScope &scope ) + { + EntityCacheNode* node = cacheNodeForId( id ); + if ( !node ) { + request( id, scope ); + return false; + } + return !node->pending; + } + /** 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 = 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 ) 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 ) delete mCache.dequeue(); } private: QQueue*> mCache; int mCapacity; }; 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<> 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 9b2ee026f..3565824bf 100644 --- a/akonadi/tests/entitycachetest.cpp +++ b/akonadi/tests/entitycachetest.cpp @@ -1,98 +1,100 @@ /* 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 ) ); + QVERIFY( !cache.retrieve( 1 ).isValid() ); cache.request( 1, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); + QVERIFY( !cache.retrieve( 1 ).isValid() ); 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"